From 1f6712663f75851e5d598daadea16a35f2d50a39 Mon Sep 17 00:00:00 2001 From: Assaf Attias <49212512+attiasas@users.noreply.github.com> Date: Sun, 3 Nov 2024 17:43:03 +0200 Subject: [PATCH] Improve Tests (#162) --- .github/PULL_REQUEST_TEMPLATE.md | 1 + .github/actions/install-and-setup/action.yml | 57 ++++ .github/workflows/analysis.yml | 63 ++-- .github/workflows/removeLabel.yml | 18 -- .github/workflows/test.yml | 258 +++++++++++++--- CONTRIBUTING.md | 44 ++- _typos.toml | 7 + artifactory_test.go | 8 +- audit_test.go | 247 ++++++--------- cli/docs/flags.go | 2 +- cli/gitcommands.go | 2 +- commands/audit/audit.go | 196 ++++++------ commands/audit/audit_test.go | 77 +---- commands/audit/sca/java/mvn.go | 4 +- commands/audit/sca/nuget/nuget.go | 4 +- commands/audit/sca/pnpm/pnpm.go | 2 +- commands/audit/sca/python/python.go | 4 +- commands/audit/scarunner.go | 41 ++- commands/curation/curationaudit.go | 34 +- commands/curation/curationaudit_test.go | 261 +++++++++------- commands/enrich/enrich.go | 16 +- commands/git/countcontributors.go | 2 +- commands/scan/buildscan.go | 4 +- commands/scan/scan.go | 291 +++++++++--------- enrich_test.go | 37 ++- git_test.go | 5 +- jas/common.go | 1 - jas/runner/jasrunner.go | 26 +- jas/runner/jasrunner_test.go | 5 +- jfrogclisecurity_test.go | 17 +- scans_test.go | 55 ++-- tests/config.go | 128 ++++++-- tests/consts.go | 28 +- .../go/missing-context/go.mod | 7 - .../go/missing-context/go.sum | 1 - .../maven/missing-context/pom.xml | 46 +++ .../test_integrationutils.go} | 151 ++++++++- tests/utils/test_utils.go | 57 ++-- unit_test.go | 8 +- utils/parallel_runner.go | 11 +- utils/results/conversion/convertor.go | 4 +- .../conversion/sarifparser/sarifparser.go | 4 +- .../simplejsonparser/simplejsonparser.go | 5 +- .../conversion/summaryparser/summaryparser.go | 2 +- .../conversion/tableparser/tableparser.go | 5 +- utils/results/results.go | 43 ++- utils/xsc/analyticsmetrics_test.go | 2 +- utils/xsc/errorreport.go | 3 +- utils/xsc/errorreport_test.go | 2 +- utils/xsc/xscmanager.go | 3 +- xray_test.go | 8 +- xsc_test.go | 64 ++-- 52 files changed, 1401 insertions(+), 970 deletions(-) create mode 100644 .github/actions/install-and-setup/action.yml delete mode 100644 .github/workflows/removeLabel.yml create mode 100644 _typos.toml delete mode 100644 tests/testdata/projects/package-managers/go/missing-context/go.mod delete mode 100644 tests/testdata/projects/package-managers/go/missing-context/go.sum create mode 100644 tests/testdata/projects/package-managers/maven/missing-context/pom.xml rename tests/utils/{test_config.go => integration/test_integrationutils.go} (66%) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 225d158c..f0dc42f3 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -3,6 +3,7 @@ - [ ] The code has been formatted properly using `go fmt ./...`. - [ ] All [static analysis checks](https://github.com/jfrog/jfrog-cli-security/actions/workflows/analysis.yml) passed. - [ ] All [tests](https://github.com/jfrog/jfrog-cli-security/actions/workflows/test.yml) have passed. If this feature is not already covered by the tests, new tests have been added. +- [ ] Updated the [Contributing page](https://github.com/jfrog/jfrog-cli-security/blob/main/CONTRIBUTING.md) / [ReadMe page](https://github.com/jfrog/jfrog-cli-security/blob/main/README.md) / [CI Workflow files](https://github.com/jfrog/jfrog-cli-security/tree/main/.github/workflows) if needed. - [ ] All changes are detailed at the description. if not already covered at [JFrog Documentation](https://github.com/jfrog/documentation), new documentation have been added. ----- \ No newline at end of file diff --git a/.github/actions/install-and-setup/action.yml b/.github/actions/install-and-setup/action.yml new file mode 100644 index 00000000..a4700cae --- /dev/null +++ b/.github/actions/install-and-setup/action.yml @@ -0,0 +1,57 @@ +name: "Install and Setup Dependencies" +description: "Install needed dependencies for this repository like Go, Node, Java, Python, etc." + +runs: + using: "composite" + steps: + # Install dependencies + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: 1.22.x + # - name: Setup Go with cache + # uses: jfrog/.github/actions/install-go-with-cache@main + + - name: Install npm + uses: actions/setup-node@v4 + with: + node-version: "16" + - name: Setup Pnpm + uses: pnpm/action-setup@v3 + with: + version: 8 + + - name: Install Java + uses: actions/setup-java@v4 + with: + java-version: "11" + distribution: "adopt" + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + with: + gradle-version: 7.6 + + - name: Install NuGet + uses: nuget/setup-nuget@v2 + with: + nuget-version: 6.11.0 + - name: Install dotnet + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '6.x' + + - name: Setup Python3 + uses: actions/setup-python@v5 + with: + python-version: "3.x" + - name: Setup Pipenv + run: python -m pip install pipenv + shell: ${{ runner.os == 'Windows' && 'powershell' || 'bash' }} + - name: Setup Poetry + run: python -m pip install poetry + shell: ${{ runner.os == 'Windows' && 'powershell' || 'bash' }} + - name: Setup Conan + run: | + python -m pip install conan + conan profile detect + shell: ${{ runner.os == 'Windows' && 'powershell' || 'bash' }} diff --git a/.github/workflows/analysis.yml b/.github/workflows/analysis.yml index 36c119a0..8178fb9d 100644 --- a/.github/workflows/analysis.yml +++ b/.github/workflows/analysis.yml @@ -6,38 +6,61 @@ on: tags-ignore: - '**' pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}-${{ github.ref }} + cancel-in-progress: true jobs: + Go-Lint: + name: Lint ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ ubuntu, windows, macos ] + runs-on: ${{ matrix.os }}-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main + + - name: Run Go vet + run: go vet -v ./... + Static-Check: runs-on: ubuntu-latest steps: - name: Checkout Source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - - name: Install Go - uses: actions/setup-go@v3 - with: - go-version: 1.22.x + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main - - name: Static Code Analysis - uses: golangci/golangci-lint-action@v3 - with: - args: | - --timeout 5m --out-${NO_FUTURE}format colored-line-number --enable errcheck,gosimple,govet,ineffassign,staticcheck,typecheck,unused,gocritic,asasalint,asciicheck,errchkjson,exportloopref,forcetypeassert,makezero,nilerr,unparam,unconvert,wastedassign,usestdlibvars + - name: Run Go vet + run: go vet -v ./... + - name: Run golangci linter + uses: jfrog/.github/actions/golangci-lint@main Go-Sec: runs-on: ubuntu-latest steps: - name: Checkout Source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - - name: Install Go - uses: actions/setup-go@v3 - with: - go-version: 1.22.x + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main + + - name: Run Go-Sec scanner + uses: jfrog/.github/actions/gosec-scanner@main - # Temporarily set version 2.18.0 to workaround https://github.com/securego/gosec/issues/1046 - - name: Run Gosec Security Scanner - uses: securego/gosec@v2.18.0 - with: - args: -exclude G204,G301,G302,G304,G306 -tests -exclude-dir \.*test\.* ./... + Check-Spelling: + name: Spell Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Check spelling + uses: crate-ci/typos@master diff --git a/.github/workflows/removeLabel.yml b/.github/workflows/removeLabel.yml deleted file mode 100644 index 67be7e8d..00000000 --- a/.github/workflows/removeLabel.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Remove Label -on: - pull_request_target: - types: [labeled] -# Ensures that only the latest commit is running for each PR at a time. -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}-${{ github.ref }} - cancel-in-progress: true -jobs: - Remove-Label: - if: contains(github.event.pull_request.labels.*.name, 'safe to test') - name: Remove label - runs-on: ubuntu-latest - steps: - - name: Remove 'safe to test' - uses: actions-ecosystem/action-remove-labels@v1 - with: - labels: "safe to test" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f2d55ea4..eb27e9dd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,4 +1,5 @@ name: JFrog CLI Security Tests + on: push: branches: @@ -8,75 +9,236 @@ on: # Triggers the workflow on labeled PRs only. pull_request_target: types: [ labeled ] + # Ensures that only the latest commit is running for each PR at a time. concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}-${{ github.ref }} cancel-in-progress: true + +# Environment variables shared across all jobs. +env: + GOPROXY: direct + GO_COMMON_TEST_ARGS: "-v github.com/jfrog/jfrog-cli-security --race --timeout 30m --jfrog.url=${{ secrets.PLATFORM_URL }} --jfrog.adminToken=${{ secrets.PLATFORM_ADMIN_TOKEN }}" + GRADLE_OPTS: -Dorg.gradle.daemon=false + CI: true + JFROG_CLI_LOG_LEVEL: DEBUG + jobs: - test: + Pretest: if: contains(github.event.pull_request.labels.*.name, 'safe to test') || github.event_name == 'push' + runs-on: ubuntu-latest + steps: + - name: Remove 'safe to test' label + uses: actions-ecosystem/action-remove-labels@v1 + if: ${{ github.event_name != 'push' }} + with: + labels: "safe to test" + + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main + + - name: Run Go vet + run: go vet -v ./... + + Unit_Tests: + name: "[${{ matrix.os }}] Unit Tests" + needs: Pretest runs-on: ${{ matrix.os }}-latest strategy: fail-fast: false matrix: os: [ ubuntu, windows, macos ] - env: - GOPROXY: direct - GRADLE_OPTS: -Dorg.gradle.daemon=false - CI: true - JFROG_CLI_LOG_LEVEL: DEBUG steps: - # Install dependencies - - name: Install Go - uses: actions/setup-go@v3 - with: - go-version: 1.22.x - - name: Install npm - uses: actions/setup-node@v3 - with: - node-version: "16" - - name: Setup Pnpm - uses: pnpm/action-setup@v3 + # Prepare the environment + - name: Checkout code + uses: actions/checkout@v4 with: - version: 8 - - name: Install Java - uses: actions/setup-java@v3 + ref: ${{ github.event.pull_request.head.sha }} + + - name: Install and Setup Dependencies + uses: ./.github/actions/install-and-setup + + # Test and generate code coverage + - name: Run tests + run: go test ${{ env.GO_COMMON_TEST_ARGS }} -cover -coverprofile=cover-unit-tests --test.unit + + - name: Archive Code Coverage Results + uses: actions/upload-artifact@v4 + if: matrix.os == 'ubuntu' with: - java-version: "11" - distribution: "adopt" - - name: Install NuGet - uses: nuget/setup-nuget@v2 + name: unit-tests-code-coverage + path: cover-unit-tests + + Audit_Command_Integration_Tests: + name: "[${{ matrix.os }}] ${{ matrix.suite.name }} Audit Command Integration Tests" + needs: Pretest + runs-on: ${{ matrix.os }}-latest + strategy: + fail-fast: false + matrix: + os: [ ubuntu, windows, macos ] + suite: + - name: 'General Suite (Detection, MultiTech, NoTech...)' + testFlags: '--test.audit' + - name: 'JAS Suite' + testFlags: '--test.audit.Jas' + - name: 'Java Script Suite (Npm, Pnpm, Yarn)' + testFlags: '--test.audit.JavaScript' + - name: 'Python Suite (Pip, Pipenv, Poetry)' + testFlags: '--test.audit.Python' + - name: 'Java Suite (Maven, Gradle)' + testFlags: '--test.audit.Java' + - name: 'Go Suite (Go Modules, Dep, Glide)' + testFlags: '--test.audit.Go' + - name: 'C/C++/C# Suite (Conan, NuGet, Dotnet)' + testFlags: '--test.audit.C' + + steps: + # Prepare the environment + - name: Checkout code + uses: actions/checkout@v4 with: - nuget-version: 6.11.0 - - name: Install dotnet - uses: actions/setup-dotnet@v3 + ref: ${{ github.event.pull_request.head.sha }} + + - name: Install and Setup Dependencies + uses: ./.github/actions/install-and-setup + + # Test + - name: Run tests + run: go test ${{ env.GO_COMMON_TEST_ARGS }} ${{ matrix.suite.testFlags }} + + Artifactory_Integration_Tests: + name: "[${{ matrix.os }}] Artifactory Integration Tests" + needs: Pretest + runs-on: ${{ matrix.os }}-latest + strategy: + fail-fast: false + matrix: + os: [ ubuntu, windows, macos ] + steps: + # Prepare the environment + - name: Checkout code + uses: actions/checkout@v4 with: - dotnet-version: '6.x' - - name: Setup Python3 - uses: actions/setup-python@v4 + ref: ${{ github.event.pull_request.head.sha }} + + - name: Install and Setup Dependencies + uses: ./.github/actions/install-and-setup + + # Test + - name: Run tests + run: go test ${{ env.GO_COMMON_TEST_ARGS }} --test.artifactory --ci.runId=${{ runner.os }}-sec-test + + + Xray_Commands_Integration_Tests: + name: "[${{ matrix.os }}] Xray Commands Integration Tests" + needs: Pretest + runs-on: ${{ matrix.os }}-latest + strategy: + fail-fast: false + matrix: + os: [ ubuntu, windows, macos ] + steps: + # Prepare the environment + - name: Checkout code + uses: actions/checkout@v4 with: - python-version: "3.x" - - name: Setup Pipenv - run: python -m pip install pipenv - - name: Setup Poetry - run: python -m pip install poetry - - name: Setup Conan - run: | - python -m pip install conan - conan profile detect - - name: Setup Gradle - uses: gradle/gradle-build-action@v2 + ref: ${{ github.event.pull_request.head.sha }} + + - name: Install and Setup Dependencies + uses: ./.github/actions/install-and-setup + + # Test + - name: Run tests + run: go test ${{ env.GO_COMMON_TEST_ARGS }} --test.xray + + Xsc_Integration_Tests: + name: "[${{ matrix.os }}] XSC Integration Tests" + needs: Pretest + runs-on: ${{ matrix.os }}-latest + strategy: + fail-fast: false + matrix: + os: [ ubuntu, windows, macos ] + steps: + # Prepare the environment + - name: Checkout code + uses: actions/checkout@v4 with: - gradle-version: 7.6 - # Checkout code + ref: ${{ github.event.pull_request.head.sha }} + + - name: Install and Setup Dependencies + uses: ./.github/actions/install-and-setup + + # Test + - name: Run tests + run: go test ${{ env.GO_COMMON_TEST_ARGS }} --test.xsc + + Other_Scan_Commands_Integration_Tests: + name: "[${{ matrix.os }}] Other Scan Commands Integration Tests" + needs: Pretest + runs-on: ${{ matrix.os }}-latest + strategy: + fail-fast: false + matrix: + os: [ ubuntu, windows, macos ] + steps: + # Prepare the environment - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} + + - name: Install and Setup Dependencies + uses: ./.github/actions/install-and-setup + # Test - - name: Run security tests (without Docker Scan) - run: go test -v github.com/jfrog/jfrog-cli-security --race --timeout 0 --test.security --jfrog.url=${{ secrets.PLATFORM_URL }} --jfrog.adminToken=${{ secrets.PLATFORM_ADMIN_TOKEN }} --jfrog.user=${{ secrets.PLATFORM_USER }} --test.containerRegistry=${{ secrets.CONTAINER_REGISTRY }} --ci.runId=${{ runner.os }}-xray + - name: Run tests + run: go test ${{ env.GO_COMMON_TEST_ARGS }} --test.scan if: ${{ matrix.os != 'ubuntu' }} - - name: Run security tests (with Docker Scan, only on Ubuntu) - run: go test -v github.com/jfrog/jfrog-cli-security --race --timeout 0 --test.security --test.dockerScan --jfrog.url=${{ secrets.PLATFORM_URL }} --jfrog.adminToken=${{ secrets.PLATFORM_ADMIN_TOKEN }} --test.containerRegistry=${{ secrets.CONTAINER_REGISTRY }} --ci.runId=${{ runner.os }}-xray + - name: Run security tests (with Docker Scan) + run: go test ${{ env.GO_COMMON_TEST_ARGS }} --test.scan --test.dockerScan --test.containerRegistry=${{ secrets.CONTAINER_REGISTRY }} --ci.runId=${{ runner.os }}-sec-test if: ${{ matrix.os == 'ubuntu' }} + + Other_Commands_Integration_Tests: + name: "[${{ matrix.os }}] Other Commands Integration Tests" + needs: Pretest + runs-on: ${{ matrix.os }}-latest + strategy: + fail-fast: false + matrix: + os: [ ubuntu, windows, macos ] + steps: + # Prepare the environment + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: Install and Setup Dependencies + uses: ./.github/actions/install-and-setup + + # Test + - name: Run tests + run: go test ${{ env.GO_COMMON_TEST_ARGS }} --test.curation --test.enrich --test.git + + Code_Coverage: + name: Generate Code Coverage Report + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + needs: [Unit_Tests] + permissions: + contents: read + actions: read # to download code coverage results from jobs + pull-requests: write # write permission needed to comment on PR + steps: + - name: Generate Unit Tests Code Coverage Report + uses: fgrosse/go-coverage-report@v1.2.0 + with: + coverage-artifact-name: unit-tests-code-coverage + coverage-file-name: cover-unit-tests \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fd821772..c74c7d0f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -56,25 +56,39 @@ go test -v github.com/jfrog/jfrog-cli-security [test-types] [flags] ### The available flags are: -| Flag | Description | -| ------------------- | ----------------------------------------------------------------------------------------------- | -| `-jfrog.url` | [Default: http://localhost:8081] JFrog platform URL | -| `-jfrog.user` | [Default: admin] JFrog platform username | -| `-jfrog.password` | [Default: password] JFrog platform password | -| `-jfrog.adminToken` | [Optional] JFrog platform admin token | -| `-ci.runId` | [Optional] A unique identifier used as a suffix to create repositories and builds in the tests. | -| `-jfrog.sshKeyPath` | [Optional] Path to the SSH key file. Use this flag only if the Artifactory URL format is `ssh://[domain]:port`. | -| `-jfrog.sshPassphrase` | [Optional] Passphrase for the SSH key. | +| Flag | Equivalent Env vars | Description | +| ---------------------- | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | +| `-jfrog.url` | `JFROG_SECURITY_CLI_TESTS_JFROG_URL` | [Default: http://localhost:8083] JFrog platform URL | +| `-jfrog.user` | `JFROG_SECURITY_CLI_TESTS_JFROG_USER` | [Default: admin] JFrog platform username | +| `-jfrog.password` | `JFROG_SECURITY_CLI_TESTS_JFROG_PASSWORD` | [Default: password] JFrog platform password | +| `-jfrog.adminToken` | `JFROG_SECURITY_CLI_TESTS_JFROG_ACCESS_TOKEN` | [Optional] JFrog platform admin token | +| `-ci.runId` | - | [Optional] A unique identifier used as a suffix to create repositories and builds in the tests. | +| `-jfrog.sshKeyPath` | - | [Optional] Path to the SSH key file. Use this flag only if the Artifactory URL format is `ssh://[domain]:port`. | +| `-jfrog.sshPassphrase` | - | [Optional] Passphrase for the SSH key. | --- -### The available test types are: - -| Type | Description | -| -------------------- | ------------------ | -| `-test.security` | [Default: true] Security commands integration tests | -| `-test.dockerScan` | [Optional] Docker scan integration tests | +### The available test types are (Not supplying flags will run all tests): + +| Type | Description | +| ------------------------ | ------------------------------------------------------------------------------------- | +| `-test.unit` | [Optional] Unit tests | +| `-test.artifactory` | [Optional] Artifactory integration tests | +| `-test.xsc` | [Optional] XSC integration tests | +| `-test.xray` | [Optional] Xray commands integration tests | +| `-test.audit` | [Optional] Audit command general (Detection, NoTech, MultiTech...) integration tests | +| `-test.audit.Jas` | [Optional] Audit command Jas integration tests | +| `-test.audit.JavaScript` | [Optional] Audit command JavaScript technologies (Npm, Pnpm, Yarn)integration tests | +| `-test.audit.Java` | [Optional] Audit command Java technologies (Maven, Gradle)integration tests | +| `-test.audit.C` | [Optional] Audit command C/C++/C# technologies (Nuget/DotNet, Conan)integration tests | +| `-test.audit.Go` | [Optional] Audit command Go integration tests | +| `-test.audit.Python` | [Optional] Audit command Python technologies (Pip, PipEnv, Poetry)integration tests | +| `-test.scan` | [Optional] Other scan commands integration tests | +| `-test.curation` | [Optional] Curation command integration tests | +| `-test.enrich` | [Optional] Enrich Command integration tests | +| `-test.git` | [Optional] Git commands integration tests | +| `-test.dockerScan` | [Optional] Docker scan command integration tests | ### Docker Scan tests diff --git a/_typos.toml b/_typos.toml new file mode 100644 index 00000000..1ca105b3 --- /dev/null +++ b/_typos.toml @@ -0,0 +1,7 @@ +[default.extend-words] +testng = "testng" +afe = "afe" +Bouned = "Bouned" + +[files] +extend-exclude = ["go.mod", "go.sum", "tests/testdata"] \ No newline at end of file diff --git a/artifactory_test.go b/artifactory_test.go index aaaf03f3..265ad74b 100644 --- a/artifactory_test.go +++ b/artifactory_test.go @@ -19,6 +19,8 @@ import ( "github.com/jfrog/jfrog-cli-security/jas" securityTests "github.com/jfrog/jfrog-cli-security/tests" securityTestUtils "github.com/jfrog/jfrog-cli-security/tests/utils" + "github.com/jfrog/jfrog-cli-security/tests/utils/integration" + securityIntegrationTestUtils "github.com/jfrog/jfrog-cli-security/tests/utils/integration" "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/generic" commonCommands "github.com/jfrog/jfrog-cli-core/v2/common/commands" @@ -34,6 +36,7 @@ import ( // We perform validation on dependency resolution from an Artifactory server during the construction of the dependency tree during 'audit' flow. // This process involves resolving all dependencies required by the project. func TestDependencyResolutionFromArtifactory(t *testing.T) { + integration.InitArtifactoryTest(t) testCases := []struct { testProjectPath []string resolveRepoName string @@ -101,7 +104,7 @@ func TestDependencyResolutionFromArtifactory(t *testing.T) { projectType: project.Poetry, }, } - securityTestUtils.CreateJfrogHomeConfig(t, true) + securityIntegrationTestUtils.CreateJfrogHomeConfig(t, true) defer securityTestUtils.CleanTestsHomeEnv() for _, testCase := range testCases { @@ -114,7 +117,7 @@ func TestDependencyResolutionFromArtifactory(t *testing.T) { func testSingleTechDependencyResolution(t *testing.T, testProjectPartialPath []string, resolveRepoName string, cacheRepoName string, projectType project.ProjectType) { tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) defer createTempDirCallback() - testProjectPath := filepath.Join(append([]string{filepath.FromSlash(securityTestUtils.GetTestResourcesPath()), "projects", "package-managers"}, testProjectPartialPath...)...) + testProjectPath := filepath.Join(append([]string{filepath.FromSlash(securityTests.GetTestResourcesPath()), "projects", "package-managers"}, testProjectPartialPath...)...) assert.NoError(t, biutils.CopyDir(testProjectPath, tempDirPath, true, nil)) rootDir, err := os.Getwd() assert.NoError(t, err) @@ -215,6 +218,7 @@ func clearOrRedirectLocalCacheIfNeeded(t *testing.T, projectType project.Project } func TestDownloadAnalyzerManagerIfNeeded(t *testing.T) { + integration.InitArtifactoryTest(t) // Configure a new JFrog CLI home dir. tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) defer createTempDirCallback() diff --git a/audit_test.go b/audit_test.go index c83c81fe..1372fa8e 100644 --- a/audit_test.go +++ b/audit_test.go @@ -3,13 +3,14 @@ package main import ( "encoding/json" "fmt" - "github.com/jfrog/jfrog-cli-security/utils/jasutils" "os" "os/exec" "path/filepath" "strings" "testing" + "github.com/jfrog/jfrog-cli-security/utils/jasutils" + "github.com/jfrog/jfrog-cli-core/v2/plugins/components" "github.com/jfrog/jfrog-cli-security/cli" @@ -17,7 +18,6 @@ import ( "github.com/jfrog/jfrog-cli-security/utils/formats" "github.com/jfrog/jfrog-cli-security/utils/validations" - testsUtils "github.com/jfrog/jfrog-cli-security/tests/utils" xrayUtils "github.com/jfrog/jfrog-client-go/xray/services/utils" "github.com/stretchr/testify/assert" @@ -30,6 +30,8 @@ import ( securityTests "github.com/jfrog/jfrog-cli-security/tests" securityTestUtils "github.com/jfrog/jfrog-cli-security/tests/utils" + "github.com/jfrog/jfrog-cli-security/tests/utils/integration" + securityIntegrationTestUtils "github.com/jfrog/jfrog-cli-security/tests/utils/integration" "github.com/jfrog/jfrog-cli-security/utils/xray/scangraph" clientTests "github.com/jfrog/jfrog-client-go/utils/tests" "github.com/jfrog/jfrog-client-go/xray/services" @@ -53,14 +55,9 @@ func TestXrayAuditNpmSimpleJson(t *testing.T) { } func testAuditNpm(t *testing.T, format string, withVuln bool) string { - securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion) - tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) - defer createTempDirCallback() - npmProjectPath := filepath.Join(filepath.FromSlash(securityTestUtils.GetTestResourcesPath()), "projects", "package-managers", "npm", "npm") - // Copy the npm project from the testdata to a temp dir - assert.NoError(t, biutils.CopyDir(npmProjectPath, tempDirPath, true, nil)) - prevWd := securityTestUtils.ChangeWD(t, tempDirPath) - defer clientTests.ChangeDirAndAssert(t, prevWd) + integration.InitAuditJavaScriptTest(t, scangraph.GraphScanMinXrayVersion) + _, cleanUp := securityTestUtils.CreateTestProjectEnvAndChdir(t, filepath.Join(filepath.FromSlash(securityTests.GetTestResourcesPath()), "projects", "package-managers", "npm", "npm")) + defer cleanUp() // Run npm install before executing jfrog xr npm-audit assert.NoError(t, exec.Command("npm", "install").Run()) // Add dummy descriptor file to check that we run only specific audit @@ -91,17 +88,12 @@ func TestXrayAuditConanSimpleJson(t *testing.T) { } func testAuditConan(t *testing.T, format string, withVuln bool) string { - securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion) - tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) - defer createTempDirCallback() - conanProjectPath := filepath.Join(filepath.FromSlash(securityTestUtils.GetTestResourcesPath()), "projects", "package-managers", "conan") - // Copy the conan project from the testdata to a temp dir - assert.NoError(t, biutils.CopyDir(conanProjectPath, tempDirPath, true, nil)) - prevWd := securityTestUtils.ChangeWD(t, tempDirPath) - defer clientTests.ChangeDirAndAssert(t, prevWd) + integration.InitAuditCTest(t, scangraph.GraphScanMinXrayVersion) + _, cleanUp := securityTestUtils.CreateTestProjectEnvAndChdir(t, filepath.Join(filepath.FromSlash(securityTests.GetTestResourcesPath()), "projects", "package-managers", "conan")) + defer cleanUp() // Run conan install before executing jfrog audit assert.NoError(t, exec.Command("conan").Run()) - watchName, deleteWatch := securityTestUtils.CreateTestWatch(t, "audit-policy", "audit-watch", xrayUtils.High) + watchName, deleteWatch := securityTestUtils.CreateTestWatch(t, "audit-curation-policy", "audit-curation-watch", xrayUtils.High) defer deleteWatch() args := []string{"audit", "--licenses", "--format=" + format, "--watches=" + watchName, "--fail=false"} if withVuln { @@ -127,14 +119,9 @@ func TestXrayAuditPnpmSimpleJson(t *testing.T) { } func testXrayAuditPnpm(t *testing.T, format string) string { - securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion) - tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) - defer createTempDirCallback() - npmProjectPath := filepath.Join(filepath.FromSlash(securityTestUtils.GetTestResourcesPath()), "projects", "package-managers", "npm", "npm-no-lock") - // Copy the npm project from the testdata to a temp dir - assert.NoError(t, biutils.CopyDir(npmProjectPath, tempDirPath, true, nil)) - prevWd := securityTestUtils.ChangeWD(t, tempDirPath) - defer clientTests.ChangeDirAndAssert(t, prevWd) + integration.InitAuditJavaScriptTest(t, scangraph.GraphScanMinXrayVersion) + _, cleanUp := securityTestUtils.CreateTestProjectEnvAndChdir(t, filepath.Join(filepath.FromSlash(securityTests.GetTestResourcesPath()), "projects", "package-managers", "npm", "npm-no-lock")) + defer cleanUp() // Run pnpm install before executing audit assert.NoError(t, exec.Command("pnpm", "install").Run()) // Add dummy descriptor file to check that we run only specific audit @@ -195,14 +182,9 @@ func TestXrayAuditYarnV1SimpleJson(t *testing.T) { } func testXrayAuditYarn(t *testing.T, projectDirName string, yarnCmd func()) { - securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion) - tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) - defer createTempDirCallback() - yarnProjectPath := filepath.Join(filepath.FromSlash(securityTestUtils.GetTestResourcesPath()), "projects", "package-managers", "yarn", projectDirName) - // Copy the Yarn project from the testdata to a temp directory - assert.NoError(t, biutils.CopyDir(yarnProjectPath, tempDirPath, true, nil)) - prevWd := securityTestUtils.ChangeWD(t, tempDirPath) - defer clientTests.ChangeDirAndAssert(t, prevWd) + integration.InitAuditJavaScriptTest(t, scangraph.GraphScanMinXrayVersion) + _, cleanUp := securityTestUtils.CreateTestProjectEnvAndChdir(t, filepath.Join(filepath.FromSlash(securityTests.GetTestResourcesPath()), "projects", "package-managers", "yarn", projectDirName)) + defer cleanUp() // Run yarn install before executing jf audit --yarn. Return error to assert according to test. assert.NoError(t, exec.Command("yarn").Run()) // Add dummy descriptor file to check that we run only specific audit @@ -316,14 +298,9 @@ func TestXrayAuditNugetSimpleJson(t *testing.T) { } func testXrayAuditNuget(t *testing.T, projectName, format string, restoreTech string) string { - securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion) - tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) - defer createTempDirCallback() - projectPath := filepath.Join(filepath.FromSlash(securityTestUtils.GetTestResourcesPath()), "projects", "package-managers", "nuget", projectName) - - assert.NoError(t, biutils.CopyDir(projectPath, tempDirPath, true, nil)) - prevWd := securityTestUtils.ChangeWD(t, tempDirPath) - defer clientTests.ChangeDirAndAssert(t, prevWd) + integration.InitAuditCTest(t, scangraph.GraphScanMinXrayVersion) + _, cleanUp := securityTestUtils.CreateTestProjectEnvAndChdir(t, filepath.Join(filepath.FromSlash(securityTests.GetTestResourcesPath()), "projects", "package-managers", "nuget", projectName)) + defer cleanUp() // Add dummy descriptor file to check that we run only specific audit addDummyPackageDescriptor(t, false) // Run NuGet/Dotnet restore before executing jfrog xr audit (NuGet) @@ -351,14 +328,9 @@ func TestXrayAuditGradleSimpleJson(t *testing.T) { } func testXrayAuditGradle(t *testing.T, format string) string { - securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion) - tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) - defer createTempDirCallback() - gradleProjectPath := filepath.Join(filepath.FromSlash(securityTestUtils.GetTestResourcesPath()), "projects", "package-managers", "gradle", "gradle") - // Copy the gradle project from the testdata to a temp dir - assert.NoError(t, biutils.CopyDir(gradleProjectPath, tempDirPath, true, nil)) - prevWd := securityTestUtils.ChangeWD(t, tempDirPath) - defer clientTests.ChangeDirAndAssert(t, prevWd) + integration.InitAuditJavaTest(t, scangraph.GraphScanMinXrayVersion) + _, cleanUp := securityTestUtils.CreateTestProjectEnvAndChdir(t, filepath.Join(filepath.FromSlash(securityTests.GetTestResourcesPath()), "projects", "package-managers", "gradle", "gradle")) + defer cleanUp() // Add dummy descriptor file to check that we run only specific audit addDummyPackageDescriptor(t, false) return securityTests.PlatformCli.RunCliCmdWithOutput(t, "audit", "--gradle", "--licenses", "--format="+format) @@ -381,21 +353,44 @@ func TestXrayAuditMavenSimpleJson(t *testing.T) { } func testXscAuditMaven(t *testing.T, format string) string { - securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion) - tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) - defer createTempDirCallback() - mvnProjectPath := filepath.Join(filepath.FromSlash(securityTestUtils.GetTestResourcesPath()), "projects", "package-managers", "maven", "maven") - // Copy the maven project from the testdata to a temp dir - assert.NoError(t, biutils.CopyDir(mvnProjectPath, tempDirPath, true, nil)) - prevWd := securityTestUtils.ChangeWD(t, tempDirPath) - defer clientTests.ChangeDirAndAssert(t, prevWd) + integration.InitAuditJavaTest(t, scangraph.GraphScanMinXrayVersion) + _, cleanUp := securityTestUtils.CreateTestProjectEnvAndChdir(t, filepath.Join(filepath.FromSlash(securityTests.GetTestResourcesPath()), "projects", "package-managers", "maven", "maven")) + defer cleanUp() // Add dummy descriptor file to check that we run only specific audit addDummyPackageDescriptor(t, false) return securityTests.PlatformCli.RunCliCmdWithOutput(t, "audit", "--mvn", "--licenses", "--format="+format) } +func TestXrayAuditGoJson(t *testing.T) { + output := testXrayAuditGo(t, false, string(format.Json), "simple-project") + validations.VerifyJsonResults(t, output, validations.ValidationParams{Licenses: 1, Vulnerabilities: 4}) +} + +func TestXrayAuditGoSimpleJson(t *testing.T) { + output := testXrayAuditGo(t, true, string(format.SimpleJson), "simple-project") + validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{Licenses: 3, Vulnerabilities: 4, NotCovered: 2, NotApplicable: 2}) +} + +func testXrayAuditGo(t *testing.T, noCreds bool, format, project string) string { + integration.InitAuditGoTest(t, scangraph.GraphScanMinXrayVersion) + _, cleanUp := securityTestUtils.CreateTestProjectEnvAndChdir(t, filepath.Join(filepath.FromSlash(securityTests.GetTestResourcesPath()), "projects", "package-managers", "go", project)) + defer cleanUp() + // Add dummy descriptor file to check that we run only specific audit + + addDummyPackageDescriptor(t, false) + + cliToRun := securityTests.PlatformCli + if noCreds { + cliToRun = securityTests.PlatformCli.WithoutCredentials() + // Configure a new server named "default" + securityIntegrationTestUtils.CreateJfrogHomeConfig(t, true) + defer securityTestUtils.CleanTestsHomeEnv() + } + return cliToRun.RunCliCmdWithOutput(t, "audit", "--go", "--licenses", "--format="+format) +} + func TestXrayAuditNoTech(t *testing.T) { - securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion) + integration.InitAuditGeneralTests(t, scangraph.GraphScanMinXrayVersion) tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) defer createTempDirCallback() prevWd := securityTestUtils.ChangeWD(t, tempDirPath) @@ -406,19 +401,15 @@ func TestXrayAuditNoTech(t *testing.T) { } func TestXrayAuditMultiProjects(t *testing.T) { - securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion) - tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) - defer createTempDirCallback() - multiProject := filepath.Join(filepath.FromSlash(securityTestUtils.GetTestResourcesPath()), "projects") - // Copy the multi project from the testdata to a temp dir - assert.NoError(t, biutils.CopyDir(multiProject, tempDirPath, true, nil)) - prevWd := securityTestUtils.ChangeWD(t, tempDirPath) - defer clientTests.ChangeDirAndAssert(t, prevWd) - workingDirsFlag := fmt.Sprintf("--working-dirs=%s, %s ,%s, %s, %s", - filepath.Join(tempDirPath, "package-managers", "maven", "maven"), filepath.Join(tempDirPath, "package-managers", "nuget", "single4.0"), - filepath.Join(tempDirPath, "package-managers", "python", "pip", "pip-project"), filepath.Join(tempDirPath, "jas", "jas"), filepath.Join(tempDirPath, "package-managers", "go", "missing-context")) + integration.InitAuditGeneralTests(t, scangraph.GraphScanMinXrayVersion) + _, cleanUp := securityTestUtils.CreateTestProjectEnvAndChdir(t, filepath.Join(filepath.FromSlash(securityTests.GetTestResourcesPath()), "projects")) + defer cleanUp() + // Set working-dirs flag with multiple projects + workingDirsFlag := fmt.Sprintf("--working-dirs=%s, %s ,%s, %s", + filepath.Join("package-managers", "maven", "maven"), filepath.Join("package-managers", "nuget", "single4.0"), + filepath.Join("package-managers", "python", "pip", "pip-project"), filepath.Join("jas", "jas")) // Configure a new server named "default" - securityTestUtils.CreateJfrogHomeConfig(t, true) + securityIntegrationTestUtils.CreateJfrogHomeConfig(t, true) defer securityTestUtils.CleanTestsHomeEnv() output := securityTests.PlatformCli.WithoutCredentials().RunCliCmdWithOutput(t, "audit", "--format="+string(format.SimpleJson), workingDirsFlag) @@ -430,7 +421,7 @@ func TestXrayAuditMultiProjects(t *testing.T) { Vulnerabilities: 35, Applicable: 3, Undetermined: 0, - NotCovered: 23, + NotCovered: 22, NotApplicable: 2, }) } @@ -462,14 +453,9 @@ func TestXrayAuditPipSimpleJsonWithRequirementsFile(t *testing.T) { } func testXrayAuditPip(t *testing.T, format, requirementsFile string) string { - securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion) - tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) - defer createTempDirCallback() - pipProjectPath := filepath.Join(filepath.FromSlash(securityTestUtils.GetTestResourcesPath()), "projects", "package-managers", "python", "pip", "pip-project") - // Copy the pip project from the testdata to a temp dir - assert.NoError(t, biutils.CopyDir(pipProjectPath, tempDirPath, true, nil)) - prevWd := securityTestUtils.ChangeWD(t, tempDirPath) - defer clientTests.ChangeDirAndAssert(t, prevWd) + integration.InitAuditPythonTest(t, scangraph.GraphScanMinXrayVersion) + _, cleanUp := securityTestUtils.CreateTestProjectEnvAndChdir(t, filepath.Join(filepath.FromSlash(securityTests.GetTestResourcesPath()), "projects", "package-managers", "python", "pip", "pip-project")) + defer cleanUp() // Add dummy descriptor file to check that we run only specific audit addDummyPackageDescriptor(t, false) args := []string{"audit", "--pip", "--licenses", "--format=" + format} @@ -497,14 +483,9 @@ func TestXrayAuditPipenvSimpleJson(t *testing.T) { } func testXrayAuditPipenv(t *testing.T, format string) string { - securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion) - tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) - defer createTempDirCallback() - pipenvProjectPath := filepath.Join(filepath.FromSlash(securityTestUtils.GetTestResourcesPath()), "projects", "package-managers", "python", "pipenv", "pipenv-project") - // Copy the pipenv project from the testdata to a temp dir - assert.NoError(t, biutils.CopyDir(pipenvProjectPath, tempDirPath, true, nil)) - prevWd := securityTestUtils.ChangeWD(t, tempDirPath) - defer clientTests.ChangeDirAndAssert(t, prevWd) + integration.InitAuditPythonTest(t, scangraph.GraphScanMinXrayVersion) + _, cleanUp := securityTestUtils.CreateTestProjectEnvAndChdir(t, filepath.Join(filepath.FromSlash(securityTests.GetTestResourcesPath()), "projects", "package-managers", "python", "pipenv", "pipenv-project")) + defer cleanUp() // Add dummy descriptor file to check that we run only specific audit addDummyPackageDescriptor(t, false) return securityTests.PlatformCli.RunCliCmdWithOutput(t, "audit", "--pipenv", "--licenses", "--format="+format) @@ -527,14 +508,9 @@ func TestXrayAuditPoetrySimpleJson(t *testing.T) { } func testXrayAuditPoetry(t *testing.T, format string) string { - securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion) - tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) - defer createTempDirCallback() - poetryProjectPath := filepath.Join(filepath.FromSlash(securityTestUtils.GetTestResourcesPath()), "projects", "package-managers", "python", "poetry", "poetry-project") - // Copy the poetry project from the testdata to a temp dir - assert.NoError(t, biutils.CopyDir(poetryProjectPath, tempDirPath, true, nil)) - prevWd := securityTestUtils.ChangeWD(t, tempDirPath) - defer clientTests.ChangeDirAndAssert(t, prevWd) + integration.InitAuditPythonTest(t, scangraph.GraphScanMinXrayVersion) + _, cleanUp := securityTestUtils.CreateTestProjectEnvAndChdir(t, filepath.Join(filepath.FromSlash(securityTests.GetTestResourcesPath()), "projects", "package-managers", "python", "poetry", "poetry-project")) + defer cleanUp() // Add dummy descriptor file to check that we run only specific audit addDummyPackageDescriptor(t, false) return securityTests.PlatformCli.RunCliCmdWithOutput(t, "audit", "--poetry", "--licenses", "--format="+format) @@ -566,8 +542,14 @@ func TestXrayAuditWithoutSastCppFlagSimpleJson(t *testing.T) { validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{}) } +func TestXrayAuditJasMissingContextSimpleJson(t *testing.T) { + output := testXrayAuditJas(t, securityTests.PlatformCli, filepath.Join("package-managers", "maven", "missing-context"), "3", false, false) + validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{MissingContext: 1}) +} + func TestXrayAuditNotEntitledForJas(t *testing.T) { - cliToRun, cleanUp := securityTestUtils.InitTestWithMockCommandOrParams(t, false, getNoJasAuditMockCommand) + integration.InitAuditGeneralTests(t, scangraph.GraphScanMinXrayVersion) + cliToRun, cleanUp := integration.InitTestWithMockCommandOrParams(t, false, getNoJasAuditMockCommand) defer cleanUp() output := testXrayAuditJas(t, cliToRun, filepath.Join("jas", "jas"), "3", false, false) validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{Vulnerabilities: 8}) @@ -605,9 +587,9 @@ func TestXrayAuditJasSimpleJson(t *testing.T) { } func TestXrayAuditJasSimpleJsonWithTokenValidation(t *testing.T) { - securityTestUtils.InitSecurityTest(t, jasutils.DynamicTokenValidationMinXrayVersion) + integration.InitAuditGeneralTests(t, jasutils.DynamicTokenValidationMinXrayVersion) output := testXrayAuditJas(t, securityTests.PlatformCli, filepath.Join("jas", "jas"), "3", true, false) - validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{Inactive: 5}) + validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{Vulnerabilities: 5, Inactive: 5}) } func TestXrayAuditJasSimpleJsonWithOneThread(t *testing.T) { @@ -644,19 +626,12 @@ func TestXrayAuditJasNoViolationsSimpleJson(t *testing.T) { } func testXrayAuditJas(t *testing.T, testCli *coreTests.JfrogCli, project string, threads string, validateSecrets, validateSastCpp bool) string { - securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion) - tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) - defer createTempDirCallback() - projectDir := filepath.Join(filepath.FromSlash(securityTestUtils.GetTestResourcesPath()), filepath.Join("projects", project)) - // Copy the multi project from the testdata to a temp dir - assert.NoError(t, biutils.CopyDir(projectDir, tempDirPath, true, nil)) + integration.InitAuditGeneralTests(t, scangraph.GraphScanMinXrayVersion) + _, cleanUp := securityTestUtils.CreateTestProjectEnvAndChdir(t, filepath.Join(filepath.FromSlash(securityTests.GetTestResourcesPath()), filepath.Join("projects", project))) + defer cleanUp() // Configure a new server named "default" - securityTestUtils.CreateJfrogHomeConfig(t, true) + securityIntegrationTestUtils.CreateJfrogHomeConfig(t, true) defer securityTestUtils.CleanTestsHomeEnv() - baseWd, err := os.Getwd() - assert.NoError(t, err) - chdirCallback := clientTests.ChangeDirWithCallback(t, baseWd, tempDirPath) - defer chdirCallback() args := []string{"audit", "--format=" + string(format.SimpleJson), "--threads=" + threads} if validateSecrets { args = append(args, "--secrets", "--validate-secrets") @@ -669,14 +644,9 @@ func testXrayAuditJas(t *testing.T, testCli *coreTests.JfrogCli, project string, } func TestXrayAuditDetectTech(t *testing.T) { - securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion) - tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) - defer createTempDirCallback() - mvnProjectPath := filepath.Join(filepath.FromSlash(securityTestUtils.GetTestResourcesPath()), "projects", "package-managers", "maven", "maven") - // Copy the maven project from the testdata to a temp dir - assert.NoError(t, biutils.CopyDir(mvnProjectPath, tempDirPath, true, nil)) - prevWd := securityTestUtils.ChangeWD(t, tempDirPath) - defer clientTests.ChangeDirAndAssert(t, prevWd) + integration.InitAuditGeneralTests(t, scangraph.GraphScanMinXrayVersion) + _, cleanUp := securityTestUtils.CreateTestProjectEnvAndChdir(t, filepath.Join(filepath.FromSlash(securityTests.GetTestResourcesPath()), "projects", "package-managers", "maven", "maven")) + defer cleanUp() // Run generic audit on mvn project with a vulnerable dependency output := securityTests.PlatformCli.RunCliCmdWithOutput(t, "audit", "--licenses", "--format="+string(format.SimpleJson)) var results formats.SimpleJsonResults @@ -687,28 +657,17 @@ func TestXrayAuditDetectTech(t *testing.T) { } func TestXrayRecursiveScan(t *testing.T) { - securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion) - tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) - defer createTempDirCallback() - projectDir := filepath.Join(filepath.FromSlash(securityTestUtils.GetTestResourcesPath()), "projects", "package-managers") + integration.InitAuditGeneralTests(t, scangraph.GraphScanMinXrayVersion) + projectDir := filepath.Join(filepath.FromSlash(securityTests.GetTestResourcesPath()), "projects", "package-managers") // Creating an inner NPM project - npmDirPath, err := os.MkdirTemp(tempDirPath, "npm-project") - assert.NoError(t, err) - npmProjectToCopyPath := filepath.Join(projectDir, "npm", "npm") - assert.NoError(t, biutils.CopyDir(npmProjectToCopyPath, npmDirPath, true, nil)) - + tempDirPath, cleanUp := securityTestUtils.CreateTestProjectEnvAndChdir(t, filepath.Join(projectDir, "npm", "npm")) + defer cleanUp() // Creating an inner .NET project dotnetDirPath, err := os.MkdirTemp(tempDirPath, "dotnet-project") assert.NoError(t, err) dotnetProjectToCopyPath := filepath.Join(projectDir, "dotnet", "dotnet-single") assert.NoError(t, biutils.CopyDir(dotnetProjectToCopyPath, dotnetDirPath, true, nil)) - curWd, err := os.Getwd() - assert.NoError(t, err) - - chDirCallback := clientTests.ChangeDirWithCallback(t, curWd, tempDirPath) - defer chDirCallback() - // We anticipate the execution of a recursive scan to encompass both the inner NPM project and the inner .NET project. output := securityTests.PlatformCli.RunCliCmdWithOutput(t, "audit", "--format=json") @@ -723,19 +682,13 @@ func TestXrayRecursiveScan(t *testing.T) { } func TestAuditOnEmptyProject(t *testing.T) { - securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion) - tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) - defer createTempDirCallback() - projectDir := filepath.Join(filepath.FromSlash(securityTestUtils.GetTestResourcesPath()), filepath.Join("projects", "empty_project", "python_project_with_no_deps")) - // Copy the multi project from the testdata to a temp dir - assert.NoError(t, biutils.CopyDir(projectDir, tempDirPath, true, nil)) + integration.InitAuditGeneralTests(t, scangraph.GraphScanMinXrayVersion) + _, cleanUp := securityTestUtils.CreateTestProjectEnvAndChdir(t, filepath.Join(filepath.FromSlash(securityTests.GetTestResourcesPath()), filepath.Join("projects", "empty_project", "python_project_with_no_deps"))) + defer cleanUp() // Configure a new server named "default" - securityTestUtils.CreateJfrogHomeConfig(t, true) + securityIntegrationTestUtils.CreateJfrogHomeConfig(t, true) defer securityTestUtils.CleanTestsHomeEnv() - baseWd, err := os.Getwd() - assert.NoError(t, err) - chdirCallback := clientTests.ChangeDirWithCallback(t, baseWd, tempDirPath) - defer chdirCallback() + output := securityTests.PlatformCli.WithoutCredentials().RunCliCmdWithOutput(t, "audit", "--format="+string(format.SimpleJson)) // No issues should be found in an empty project validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{}) @@ -744,7 +697,7 @@ func TestAuditOnEmptyProject(t *testing.T) { // xray-url only - the following tests check the case of adding "xray-url", instead of "url", which is the more common one func TestXrayAuditNotEntitledForJasWithXrayUrl(t *testing.T) { - cliToRun, cleanUp := securityTestUtils.InitTestWithMockCommandOrParams(t, true, getNoJasAuditMockCommand) + cliToRun, cleanUp := integration.InitTestWithMockCommandOrParams(t, true, getNoJasAuditMockCommand) defer cleanUp() output := testXrayAuditJas(t, cliToRun, filepath.Join("jas", "jas"), "3", false, false) // Verify that scan results are printed @@ -754,7 +707,7 @@ func TestXrayAuditNotEntitledForJasWithXrayUrl(t *testing.T) { } func TestXrayAuditJasSimpleJsonWithXrayUrl(t *testing.T) { - cliToRun := testsUtils.GetTestCli(cli.GetJfrogCliSecurityApp(), true) + cliToRun := integration.GetTestCli(cli.GetJfrogCliSecurityApp(), true) output := testXrayAuditJas(t, cliToRun, filepath.Join("jas", "jas"), "3", false, false) validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{ Sast: 1, diff --git a/cli/docs/flags.go b/cli/docs/flags.go index 1f2c0ce4..6ac7f619 100644 --- a/cli/docs/flags.go +++ b/cli/docs/flags.go @@ -269,7 +269,7 @@ var flagsMap = map[string]components.Flag{ InputFile: components.NewStringFlag(InputFile, "Path to an input file in YAML format contains multiple git providers. With this option, all other scm flags will be ignored and only git servers mentioned in the file will be examined.."), ScmType: components.NewStringFlag(ScmType, fmt.Sprintf("SCM type. Possible values are: %s.", git.NewScmType().GetValidScmTypeString()), components.SetMandatory()), ScmApiUrl: components.NewStringFlag(ScmApiUrl, "SCM API URL. For example: 'https://api.github.com'.", components.SetMandatory()), - Token: components.NewStringFlag(Token, fmt.Sprintf("SCM API token. In the absence of a flag, tokens should be passed in the %s enviroment variable, or in the corresponding environment variables '%s'.", git.GenericGitTokenEnvVar, git.NewScmType().GetOptionalScmTypeTokenEnvVars()), components.SetMandatory()), + Token: components.NewStringFlag(Token, fmt.Sprintf("SCM API token. In the absence of a flag, tokens should be passed in the %s environment variable, or in the corresponding environment variables '%s'.", git.GenericGitTokenEnvVar, git.NewScmType().GetOptionalScmTypeTokenEnvVars()), components.SetMandatory()), Owner: components.NewStringFlag(Owner, "The format of the owner key depends on the Git provider: On GitHub and GitLab, the owner is typically an individual or an organization, On Bitbucket, the owner can also be a project. In the case of a private instance on Bitbucket, the individual or organization name should be prefixed with '~'.", components.SetMandatory()), RepoName: components.NewStringFlag(RepoName, "List of semicolon-separated(;) repositories names to analyze, If not provided all repositories related to the provided owner will be analyzed."), Months: components.NewStringFlag(Months, "Number of months to analyze.", components.WithIntDefaultValue(git.DefaultContContributorsMonths)), diff --git a/cli/gitcommands.go b/cli/gitcommands.go index b26d64b8..69b799a3 100644 --- a/cli/gitcommands.go +++ b/cli/gitcommands.go @@ -63,7 +63,7 @@ func GetCountContributorsParams(c *components.Context) (*git.CountContributorsPa if envVarToken != "" { params.Token = envVarToken } else { - return nil, errorutils.CheckErrorf("Providing a token is mandatory. should use --%s flag, the token enviromment variable %s, or coresponding provider enviromment variable %s.", flags.Token, git.GenericGitTokenEnvVar, scmTypes.GetOptionalScmTypeTokenEnvVars()) + return nil, errorutils.CheckErrorf("Providing a token is mandatory. should use --%s flag, the token environment variable %s, or corresponding provider environment variable %s.", flags.Token, git.GenericGitTokenEnvVar, scmTypes.GetOptionalScmTypeTokenEnvVars()) } } } diff --git a/commands/audit/audit.go b/commands/audit/audit.go index 1cb707f9..83112c9e 100644 --- a/commands/audit/audit.go +++ b/commands/audit/audit.go @@ -3,7 +3,9 @@ package audit import ( "errors" "fmt" + "strings" + "github.com/jfrog/gofrog/parallel" jfrogappsconfig "github.com/jfrog/jfrog-apps-config/go" "github.com/jfrog/jfrog-cli-core/v2/common/format" "github.com/jfrog/jfrog-cli-core/v2/utils/config" @@ -126,14 +128,12 @@ func (auditCmd *AuditCommand) Run() (err error) { SetScansResultsOutputDir(auditCmd.scanResultsOutputDir) auditParams.SetIsRecursiveScan(isRecursiveScan).SetExclusions(auditCmd.Exclusions()) - auditResults, err := RunAudit(auditParams) - if err != nil { - return - } + auditResults := RunAudit(auditParams) auditCmd.analyticsMetricsService.UpdateGeneralEvent(auditCmd.analyticsMetricsService.CreateXscAnalyticsGeneralEventFinalizeFromAuditResults(auditResults)) + if auditCmd.Progress() != nil { if err = auditCmd.Progress().Quit(); err != nil { - return + return errors.Join(err, auditResults.GetErrors()) } } var messages []string @@ -149,7 +149,7 @@ func (auditCmd *AuditCommand) Run() (err error) { SetExtraMessages(messages). SetSubScansPreformed(auditCmd.ScansToPerform()). PrintScanResults(); err != nil { - return + return errors.Join(err, auditResults.GetErrors()) } if err = auditResults.GetErrors(); err != nil { @@ -174,49 +174,29 @@ func (auditCmd *AuditCommand) HasViolationContext() bool { // Runs an audit scan based on the provided auditParams. // Returns an audit Results object containing all the scan results. // If the current server is entitled for JAS, the advanced security results will be included in the scan results. -func RunAudit(auditParams *AuditParams) (cmdResults *results.SecurityCommandResults, err error) { - // Prepare - serverDetails, err := auditParams.ServerDetails() - if err != nil { - return - } - var xrayManager *xray.XrayServicesManager - if xrayManager, auditParams.xrayVersion, err = xrayutils.CreateXrayServiceManagerAndGetVersion(serverDetails); err != nil { - return - } - if err = clientutils.ValidateMinimumVersion(clientutils.Xray, auditParams.xrayVersion, scangraph.GraphScanMinXrayVersion); err != nil { - return - } - entitledForJas, err := isEntitledForJas(xrayManager, auditParams) - if err != nil { +func RunAudit(auditParams *AuditParams) (cmdResults *results.SecurityCommandResults) { + // Initialize Results struct + if cmdResults = initAuditCmdResults(auditParams); cmdResults.GeneralError != nil { return } - // Initialize Results struct - cmdResults = initCmdResults( - entitledForJas, - jas.CheckForSecretValidation(xrayManager, auditParams.xrayVersion, slices.Contains(auditParams.AuditBasicParams.ScansToPerform(), utils.SecretTokenValidationScan)), - auditParams, - ) jfrogAppsConfig, err := jas.CreateJFrogAppsConfig(cmdResults.GetTargetsPaths()) if err != nil { - return cmdResults, fmt.Errorf("failed to create JFrogAppsConfig: %s", err.Error()) + return cmdResults.AddGeneralError(fmt.Errorf("failed to create JFrogAppsConfig: %s", err.Error())) } // Initialize the parallel runner auditParallelRunner := utils.CreateSecurityParallelRunner(auditParams.threads) - auditParallelRunner.ErrWg.Add(1) // Add the JAS scans to the parallel runner var jasScanner *jas.JasScanner - var jasScanErr error - if jasScanner, jasScanErr = RunJasScans(auditParallelRunner, auditParams, cmdResults, jfrogAppsConfig); jasScanErr != nil { - auditParallelRunner.AddErrorToChan(jasScanErr) + var generalJasScanErr error + if jasScanner, generalJasScanErr = RunJasScans(auditParallelRunner, auditParams, cmdResults, jfrogAppsConfig); generalJasScanErr != nil { + cmdResults.AddGeneralError(fmt.Errorf("An error has occurred during JAS scan process. JAS scan is skipped for the following directories: %s\n%s", strings.Join(cmdResults.GetTargetsPaths(), ","), generalJasScanErr.Error())) } if auditParams.Progress() != nil { auditParams.Progress().SetHeadlineMsg("Scanning for issues") } // The sca scan doesn't require the analyzer manager, so it can run separately from the analyzer manager download routine. - if scaScanErr := buildDepTreeAndRunScaScan(auditParallelRunner, auditParams, cmdResults); scaScanErr != nil { - // If error to be caught, we add it to the auditParallelRunner error queue and continue. The error need not be returned - _ = createErrorIfPartialResultsDisabled(auditParams, auditParallelRunner, fmt.Sprintf("An error has occurred during SCA scan process. SCA scan is skipped for the following directories: %s.", auditParams.workingDirs), scaScanErr) + if generalScaScanError := buildDepTreeAndRunScaScan(auditParallelRunner, auditParams, cmdResults); generalScaScanError != nil { + cmdResults.AddGeneralError(fmt.Errorf("An error has occurred during SCA scan process. SCA scan is skipped for the following directories: %s\n%s", strings.Join(cmdResults.GetTargetsPaths(), ","), generalScaScanError.Error())) } go func() { auditParallelRunner.ScaScansWg.Wait() @@ -224,20 +204,11 @@ func RunAudit(auditParams *AuditParams) (cmdResults *results.SecurityCommandResu // Wait for all jas scanners to complete before cleaning up scanners temp dir auditParallelRunner.JasScannersWg.Wait() if jasScanner != nil && jasScanner.ScannerDirCleanupFunc != nil { - auditParallelRunner.AddErrorToChan(jasScanner.ScannerDirCleanupFunc()) + cmdResults.AddGeneralError(jasScanner.ScannerDirCleanupFunc()) } - close(auditParallelRunner.ErrorsQueue) auditParallelRunner.Runner.Done() }() - // a new routine that collects errors from the err channel into results object - go func() { - defer auditParallelRunner.ErrWg.Done() - for e := range auditParallelRunner.ErrorsQueue { - cmdResults.Error = errors.Join(cmdResults.Error, e) - } - }() auditParallelRunner.Runner.Run() - auditParallelRunner.ErrWg.Wait() return } @@ -249,74 +220,103 @@ func isEntitledForJas(xrayManager *xray.XrayServicesManager, auditParams *AuditP return jas.IsEntitledForJas(xrayManager, auditParams.xrayVersion) } -func RunJasScans(auditParallelRunner *utils.SecurityParallelRunner, auditParams *AuditParams, scanResults *results.SecurityCommandResults, jfrogAppsConfig *jfrogappsconfig.JFrogAppsConfig) (jasScanner *jas.JasScanner, err error) { +func RunJasScans(auditParallelRunner *utils.SecurityParallelRunner, auditParams *AuditParams, scanResults *results.SecurityCommandResults, jfrogAppsConfig *jfrogappsconfig.JFrogAppsConfig) (jasScanner *jas.JasScanner, generalError error) { if !scanResults.EntitledForJas { log.Info("Not entitled for JAS, skipping advance security scans...") return } serverDetails, err := auditParams.ServerDetails() if err != nil { - err = fmt.Errorf("failed to get server details: %s", err.Error()) + generalError = fmt.Errorf("failed to get server details: %s", err.Error()) return } auditParallelRunner.ResultsMu.Lock() jasScanner, err = jas.CreateJasScanner(serverDetails, scanResults.SecretValidation, auditParams.minSeverityFilter, jas.GetAnalyzerManagerXscEnvVars(auditParams.commonGraphScanParams.MultiScanId, scanResults.GetTechnologies()...), auditParams.Exclusions()...) auditParallelRunner.ResultsMu.Unlock() if err != nil { - err = fmt.Errorf("failed to create jas scanner: %s", err.Error()) + generalError = fmt.Errorf("failed to create jas scanner: %s", err.Error()) return } else if jasScanner == nil { log.Debug("Jas scanner was not created, skipping advance security scans...") return } auditParallelRunner.JasWg.Add(1) - if _, jasErr := auditParallelRunner.Runner.AddTaskWithError(func(threadId int) error { - return downloadAnalyzerManagerAndRunScanners(auditParallelRunner, scanResults, serverDetails, auditParams, jasScanner, jfrogAppsConfig, threadId) - }, auditParallelRunner.AddErrorToChan); jasErr != nil { - auditParallelRunner.AddErrorToChan(fmt.Errorf("failed to create AM downloading task, skipping JAS scans...: %s", jasErr.Error())) + if _, jasErr := auditParallelRunner.Runner.AddTaskWithError(createJasScansTasks(auditParallelRunner, scanResults, serverDetails, auditParams, jasScanner, jfrogAppsConfig), func(taskErr error) { + generalError = errors.Join(generalError, fmt.Errorf("failed while adding JAS scan tasks: %s", taskErr.Error())) + }); jasErr != nil { + generalError = fmt.Errorf("failed to create JAS task: %s", jasErr.Error()) } return } -func downloadAnalyzerManagerAndRunScanners(auditParallelRunner *utils.SecurityParallelRunner, scanResults *results.SecurityCommandResults, - serverDetails *config.ServerDetails, auditParams *AuditParams, scanner *jas.JasScanner, jfrogAppsConfig *jfrogappsconfig.JFrogAppsConfig, threadId int) (err error) { - defer func() { - auditParallelRunner.JasWg.Done() - }() - if err = jas.DownloadAnalyzerManagerIfNeeded(threadId); err != nil { - return fmt.Errorf("%s failed to download analyzer manager: %s", clientutils.GetLogMsgPrefix(threadId, false), err.Error()) - } - // Run JAS scanners for each scan target - for _, scan := range scanResults.Targets { - module := jas.GetModule(scan.Target, jfrogAppsConfig) - if module == nil { - scan.AddError(fmt.Errorf("can't find module for path %s", scan.Target)) - continue +func createJasScansTasks(auditParallelRunner *utils.SecurityParallelRunner, scanResults *results.SecurityCommandResults, + serverDetails *config.ServerDetails, auditParams *AuditParams, scanner *jas.JasScanner, jfrogAppsConfig *jfrogappsconfig.JFrogAppsConfig) parallel.TaskFunc { + return func(threadId int) (generalError error) { + defer func() { + auditParallelRunner.JasWg.Done() + }() + logPrefix := clientutils.GetLogMsgPrefix(threadId, false) + // First download the analyzer manager if needed + if err := jas.DownloadAnalyzerManagerIfNeeded(threadId); err != nil { + return fmt.Errorf("%s failed to download analyzer manager: %s", logPrefix, err.Error()) } - params := runner.JasRunnerParams{ - Runner: auditParallelRunner, - ServerDetails: serverDetails, - Scanner: scanner, - Module: *module, - ConfigProfile: auditParams.configProfile, - ScansToPreform: auditParams.ScansToPerform(), - SecretsScanType: secrets.SecretsScannerType, - DirectDependencies: auditParams.DirectDependencies(), - ThirdPartyApplicabilityScan: auditParams.thirdPartyApplicabilityScan, - ApplicableScanType: applicability.ApplicabilityScannerType, - SignedDescriptions: auditParams.OutputFormat() == format.Sarif, - ScanResults: scan, - TargetOutputDir: auditParams.scanResultsOutputDir, - } - if err = runner.AddJasScannersTasks(params); err != nil { - return fmt.Errorf("%s failed to run JAS scanners: %s", clientutils.GetLogMsgPrefix(threadId, false), err.Error()) + // Run JAS scanners for each scan target + for _, targetResult := range scanResults.Targets { + module := jas.GetModule(targetResult.Target, jfrogAppsConfig) + if module == nil { + _ = targetResult.AddTargetError(fmt.Errorf("can't find module for path %s", targetResult.Target), auditParams.AllowPartialResults()) + continue + } + params := runner.JasRunnerParams{ + Runner: auditParallelRunner, + ServerDetails: serverDetails, + Scanner: scanner, + Module: *module, + ConfigProfile: auditParams.configProfile, + ScansToPreform: auditParams.ScansToPerform(), + SecretsScanType: secrets.SecretsScannerType, + DirectDependencies: auditParams.DirectDependencies(), + ThirdPartyApplicabilityScan: auditParams.thirdPartyApplicabilityScan, + ApplicableScanType: applicability.ApplicabilityScannerType, + SignedDescriptions: auditParams.OutputFormat() == format.Sarif, + ScanResults: targetResult, + TargetOutputDir: auditParams.scanResultsOutputDir, + } + if generalError := runner.AddJasScannersTasks(params); generalError != nil { + _ = targetResult.AddTargetError(fmt.Errorf("%s failed to add JAS scan tasks: %s", logPrefix, generalError.Error()), auditParams.AllowPartialResults()) + } } + return } - return } -func initCmdResults(entitledForJas, secretValidation bool, params *AuditParams) (cmdResults *results.SecurityCommandResults) { - cmdResults = results.NewCommandResults(utils.SourceCode, params.xrayVersion, entitledForJas, secretValidation).SetMultiScanId(params.commonGraphScanParams.MultiScanId) +func initAuditCmdResults(params *AuditParams) (cmdResults *results.SecurityCommandResults) { + cmdResults = results.NewCommandResults(utils.SourceCode) + // Initialize general information + serverDetails, err := params.ServerDetails() + if err != nil { + return cmdResults.AddGeneralError(err) + } + var xrayManager *xray.XrayServicesManager + if xrayManager, params.xrayVersion, err = xrayutils.CreateXrayServiceManagerAndGetVersion(serverDetails); err != nil { + return cmdResults.AddGeneralError(err) + } else { + cmdResults.SetXrayVersion(params.xrayVersion) + } + if err = clientutils.ValidateMinimumVersion(clientutils.Xray, params.xrayVersion, scangraph.GraphScanMinXrayVersion); err != nil { + return cmdResults.AddGeneralError(err) + } + entitledForJas, err := isEntitledForJas(xrayManager, params) + if err != nil { + return cmdResults.AddGeneralError(err) + } else { + cmdResults.SetEntitledForJas(entitledForJas) + } + if entitledForJas { + cmdResults.SetSecretValidation(jas.CheckForSecretValidation(xrayManager, params.xrayVersion, slices.Contains(params.AuditBasicParams.ScansToPerform(), utils.SecretTokenValidationScan))) + } + cmdResults.SetMultiScanId(params.commonGraphScanParams.MultiScanId) + // Initialize targets detectScanTargets(cmdResults, params) if params.IsRecursiveScan() && len(params.workingDirs) == 1 && len(cmdResults.Targets) == 0 { // No SCA targets were detected, add the root directory as a target for JAS scans. @@ -360,27 +360,3 @@ func detectScanTargets(cmdResults *results.SecurityCommandResults, params *Audit } } } - -// This function checks if partial results are allowed. If so we log the error and continue. -// If partial results are not allowed and a SecurityParallelRunner is provided we add the error to its error queue and return without an error, since the errors will be later collected from the queue. -// If partial results are not allowed and a SecurityParallelRunner is not provided we return the error. -func createErrorIfPartialResultsDisabled(auditParams *AuditParams, auditParallelRunner *utils.SecurityParallelRunner, extraMassageForLog string, err error) error { - if err == nil { - return nil - } - - if auditParams.AllowPartialResults() { - if extraMassageForLog == "" { - extraMassageForLog = "An error has occurred during the audit scans" - } - log.Warn(fmt.Sprintf("%s\nSince partial results are allowed, the error is skipped: %s", extraMassageForLog, err.Error())) - return nil - } - - // When SecurityParallelRunner is provided we add the error to the queue, otherwise we return the error - if auditParallelRunner != nil { - auditParallelRunner.AddErrorToChan(err) - return nil - } - return err -} diff --git a/commands/audit/audit_test.go b/commands/audit/audit_test.go index 54960817..6dac6e19 100644 --- a/commands/audit/audit_test.go +++ b/commands/audit/audit_test.go @@ -1,7 +1,6 @@ package audit import ( - "errors" "fmt" "net/http" "path/filepath" @@ -172,7 +171,7 @@ func TestDetectScansToPreform(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - results := results.NewCommandResults(utils.SourceCode, "", true, true) + results := results.NewCommandResults(utils.SourceCode).SetEntitledForJas(true).SetSecretValidation(true) detectScanTargets(results, test.params()) if assert.Len(t, results.Targets, len(test.expected)) { for i := range results.Targets { @@ -296,8 +295,8 @@ func TestAuditWithConfigProfile(t *testing.T) { }) auditParams.SetWorkingDirs([]string{tempDirPath}).SetIsRecursiveScan(true) - auditResults, err := RunAudit(auditParams) - assert.NoError(t, err) + auditResults := RunAudit(auditParams) + assert.NoError(t, auditResults.GetErrors()) // Currently, the only supported scanners are Secrets and Sast, therefore if a config profile is utilized - all other scanners are disabled. summary, err := conversion.NewCommandResultsConvertor(conversion.ResultConvertParams{IncludeVulnerabilities: true, HasViolationContext: true}).ConvertToSummary(auditResults) @@ -337,8 +336,8 @@ func TestAuditWithScansOutputDir(t *testing.T) { SetScansResultsOutputDir(outputDirPath) auditParams.SetIsRecursiveScan(true) - _, err := RunAudit(auditParams) - assert.NoError(t, err) + auditResults := RunAudit(auditParams) + assert.NoError(t, auditResults.GetErrors()) filesList, err := fileutils.ListFiles(outputDirPath, false) assert.NoError(t, err) @@ -431,72 +430,12 @@ func TestAuditWithPartialResults(t *testing.T) { }) auditParams.SetIsRecursiveScan(true) - scanResults, err := RunAudit(auditParams) + auditResults := RunAudit(auditParams) if testcase.allowPartialResults { - assert.NoError(t, scanResults.GetErrors()) - assert.NoError(t, err) + assert.NoError(t, auditResults.GetErrors()) } else { - assert.Error(t, scanResults.GetErrors()) - assert.NoError(t, err) + assert.Error(t, auditResults.GetErrors()) } }) } } - -func TestCreateErrorIfPartialResultsDisabled(t *testing.T) { - testcases := []struct { - name string - allowPartialResults bool - auditParallelRunner bool - }{ - { - name: "Allow partial results - no error expected", - allowPartialResults: true, - auditParallelRunner: true, - }, - { - name: "Partial results disabled with SecurityParallelRunner", - allowPartialResults: false, - auditParallelRunner: true, - }, - { - name: "Partial results disabled without SecurityParallelRunner", - allowPartialResults: false, - auditParallelRunner: false, - }, - } - - for _, testcase := range testcases { - t.Run(testcase.name, func(t *testing.T) { - auditBasicParams := (&utils.AuditBasicParams{}).SetAllowPartialResults(testcase.allowPartialResults) - auditParams := NewAuditParams().SetGraphBasicParams(auditBasicParams) - - var auditParallelRunner *utils.SecurityParallelRunner - if testcase.auditParallelRunner { - auditParallelRunner = utils.CreateSecurityParallelRunner(1) - } - - err := createErrorIfPartialResultsDisabled(auditParams, auditParallelRunner, "", errors.New("error")) - if testcase.allowPartialResults { - assert.NoError(t, err) - } else { - if testcase.auditParallelRunner { - assert.False(t, isErrorsQueueEmpty(auditParallelRunner)) - } else { - assert.Error(t, err) - } - } - }) - } -} - -func isErrorsQueueEmpty(spr *utils.SecurityParallelRunner) bool { - select { - case <-spr.ErrorsQueue: - // Channel is not empty - return false - default: - // Channel is empty - return true - } -} diff --git a/commands/audit/sca/java/mvn.go b/commands/audit/sca/java/mvn.go index cda87097..1f56c3e4 100644 --- a/commands/audit/sca/java/mvn.go +++ b/commands/audit/sca/java/mvn.go @@ -89,7 +89,7 @@ func buildMavenDependencyTree(params *DepTreeParams) (dependencyTree []*xrayUtil } // Runs maven-dep-tree according to cmdName. Returns the plugin output along with a function pointer to revert the plugin side effects. -// If a non-nil clearMavenDepTreeRun pointer is returnes it means we had no error during the entire function execution +// If a non-nil clearMavenDepTreeRun pointer is returns it means we had no error during the entire function execution func (mdt *MavenDepTreeManager) RunMavenDepTree() (depTreeOutput string, clearMavenDepTreeRun func() error, err error) { // depTreeExecDir is a temp directory for all the files that are required for the maven-dep-tree run depTreeExecDir, clearMavenDepTreeRun, err := mdt.CreateTempDirWithSettingsXmlIfNeeded() @@ -254,7 +254,7 @@ func (mdt *MavenDepTreeManager) createSettingsXmlWithConfiguredArtifactory(setti } // Creates a temporary directory. -// If Artifactory resolution repo is provided, a settings.xml file with the provided server and repository will be created inside the temprary directory. +// If Artifactory resolution repo is provided, a settings.xml file with the provided server and repository will be created inside the temporarily directory. func (mdt *MavenDepTreeManager) CreateTempDirWithSettingsXmlIfNeeded() (tempDirPath string, clearMavenDepTreeRun func() error, err error) { tempDirPath, err = fileutils.CreateTempDir() if err != nil { diff --git a/commands/audit/sca/nuget/nuget.go b/commands/audit/sca/nuget/nuget.go index ffd76630..72280f65 100644 --- a/commands/audit/sca/nuget/nuget.go +++ b/commands/audit/sca/nuget/nuget.go @@ -122,7 +122,7 @@ func runDotnetRestoreAndLoadSolution(params utils.AuditParams, tmpWd, exclusionP // Determine if the project is a NuGet or .NET project toolName, err = getProjectToolName(tmpWd) if err != nil { - err = fmt.Errorf("failed while checking for the porject's tool type: %s", err.Error()) + err = fmt.Errorf("failed while checking for the project's tool type: %s", err.Error()) return } } @@ -207,7 +207,7 @@ func getProjectToolName(wd string) (toolName string, err error) { func getProjectConfigurationFilesPaths(wd string) (projectConfigFilesPaths []string, err error) { err = filepath.WalkDir(wd, func(path string, d fs.DirEntry, innerErr error) error { if innerErr != nil { - return fmt.Errorf("error has occured when trying to access or traverse the files system: %s", err.Error()) + return fmt.Errorf("error has occurred when trying to access or traverse the files system: %s", err.Error()) } if strings.HasSuffix(path, csprojFileSuffix) || strings.HasSuffix(path, packagesConfigFileName) { diff --git a/commands/audit/sca/pnpm/pnpm.go b/commands/audit/sca/pnpm/pnpm.go index aec0f134..a22c5cdd 100644 --- a/commands/audit/sca/pnpm/pnpm.go +++ b/commands/audit/sca/pnpm/pnpm.go @@ -117,7 +117,7 @@ func installProjectIfNeeded(pnpmExecPath, workingDir string) (dirForDependencies } }() - // Exclude Visual Studio inner directorty since it is not neccessary for the scan process and may cause race condition. + // Exclude Visual Studio inner directory since it is not necessary for the scan process and may cause race condition. err = biutils.CopyDir(workingDir, dirForDependenciesCalculation, true, []string{sca.DotVsRepoSuffix}) if err != nil { err = fmt.Errorf("failed copying project to temp dir: %w", err) diff --git a/commands/audit/sca/python/python.go b/commands/audit/sca/python/python.go index eaff7e2e..d940ae45 100644 --- a/commands/audit/sca/python/python.go +++ b/commands/audit/sca/python/python.go @@ -94,7 +94,7 @@ func getDependencies(auditPython *AuditPython) (dependenciesGraph map[string][]s ) }() - // Exclude Visual Studio inner directorty since it is not neccessary for the scan process and may cause race condition. + // Exclude Visual Studio inner directory since it is not necessary for the scan process and may cause race condition. err = biutils.CopyDir(wd, tempDirPath, true, []string{sca.DotVsRepoSuffix}) if err != nil { return @@ -440,4 +440,4 @@ func populatePythonDependencyTree(currNode *xrayUtils.GraphNode, dependenciesGra currNode.Nodes = append(currNode.Nodes, childNode) populatePythonDependencyTree(childNode, dependenciesGraph, uniqueDepsSet) } -} \ No newline at end of file +} diff --git a/commands/audit/scarunner.go b/commands/audit/scarunner.go index 5794e890..45ade0c8 100644 --- a/commands/audit/scarunner.go +++ b/commands/audit/scarunner.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + biutils "github.com/jfrog/build-info-go/utils" "github.com/jfrog/build-info-go/utils/pythonutils" "github.com/jfrog/jfrog-cli-security/commands/audit/sca/conan" @@ -51,7 +52,7 @@ func hasAtLeastOneTech(cmdResults *results.SecurityCommandResults) bool { return false } -func buildDepTreeAndRunScaScan(auditParallelRunner *utils.SecurityParallelRunner, auditParams *AuditParams, cmdResults *results.SecurityCommandResults) (err error) { +func buildDepTreeAndRunScaScan(auditParallelRunner *utils.SecurityParallelRunner, auditParams *AuditParams, cmdResults *results.SecurityCommandResults) (generalError error) { if len(auditParams.ScansToPerform()) > 0 && !slices.Contains(auditParams.ScansToPerform(), xrayutils.ScaScan) { log.Debug("Skipping SCA scan as requested by input...") return @@ -60,14 +61,13 @@ func buildDepTreeAndRunScaScan(auditParallelRunner *utils.SecurityParallelRunner log.Debug("Skipping SCA scan as a configuration profile is being utilized and currently only Secrets and Sast scanners are supported when utilizing a configuration profile") return } - // Prepare - currentWorkingDir, err := os.Getwd() - if errorutils.CheckError(err) != nil { + currentWorkingDir, generalError := os.Getwd() + if errorutils.CheckError(generalError) != nil { return } - serverDetails, err := auditParams.ServerDetails() - if err != nil { + serverDetails, generalError := auditParams.ServerDetails() + if generalError != nil { return } if !hasAtLeastOneTech(cmdResults) { @@ -76,33 +76,34 @@ func buildDepTreeAndRunScaScan(auditParallelRunner *utils.SecurityParallelRunner } defer func() { // Make sure to return to the original working directory, buildDependencyTree may change it - err = errors.Join(err, os.Chdir(currentWorkingDir)) + generalError = errors.Join(generalError, errorutils.CheckError(os.Chdir(currentWorkingDir))) }() // Preform SCA scans - for _, scan := range cmdResults.Targets { - if scan.Technology == "" { - log.Warn(fmt.Sprintf("Couldn't determine a package manager or build tool used by this project. Skipping the SCA scan in '%s'...", scan.Target)) + for _, targetResult := range cmdResults.Targets { + if targetResult.Technology == "" { + log.Warn(fmt.Sprintf("Couldn't determine a package manager or build tool used by this project. Skipping the SCA scan in '%s'...", targetResult.Target)) continue } // Get the dependency tree for the technology in the working directory. - treeResult, bdtErr := buildDependencyTree(scan, auditParams) + treeResult, bdtErr := buildDependencyTree(targetResult, auditParams) if bdtErr != nil { var projectNotInstalledErr *biutils.ErrProjectNotInstalled if errors.As(bdtErr, &projectNotInstalledErr) { log.Warn(bdtErr.Error()) continue } - err = errors.Join(err, createErrorIfPartialResultsDisabled(auditParams, nil, fmt.Sprintf("Dependencies tree construction ha failed for the following target: %s", scan.Target), fmt.Errorf("failed to get dependencies trees in '%s':\n%s", scan.Target, bdtErr.Error()))) + _ = targetResult.AddTargetError(fmt.Errorf("Failed to build dependency tree: %s", bdtErr.Error()), auditParams.AllowPartialResults()) continue } // Create sca scan task auditParallelRunner.ScaScansWg.Add(1) - _, taskErr := auditParallelRunner.Runner.AddTaskWithError(executeScaScanTask(auditParallelRunner, serverDetails, auditParams, scan, treeResult), func(err error) { - _ = createErrorIfPartialResultsDisabled(auditParams, auditParallelRunner, fmt.Sprintf("Failed to execute SCA scan for the following target: %s", scan.Target), fmt.Errorf("SCA scan failed in '%s':\n%s", scan.Target, err.Error())) - auditParallelRunner.ScaScansWg.Done() + // defer auditParallelRunner.ScaScansWg.Done() + _, taskErr := auditParallelRunner.Runner.AddTaskWithError(executeScaScanTask(auditParallelRunner, serverDetails, auditParams, targetResult, treeResult), func(err error) { + _ = targetResult.AddTargetError(fmt.Errorf("Failed to execute SCA scan: %s", err.Error()), auditParams.AllowPartialResults()) }) if taskErr != nil { - return fmt.Errorf("failed to create sca scan task for '%s': %s", scan.Target, taskErr.Error()) + _ = targetResult.AddTargetError(fmt.Errorf("Failed to create SCA scan task: %s", taskErr.Error()), auditParams.AllowPartialResults()) + auditParallelRunner.ScaScansWg.Done() } } return @@ -120,14 +121,8 @@ func getRequestedDescriptors(params *AuditParams) map[techutils.Technology][]str func executeScaScanTask(auditParallelRunner *utils.SecurityParallelRunner, serverDetails *config.ServerDetails, auditParams *AuditParams, scan *results.TargetResults, treeResult *DependencyTreeResult) parallel.TaskFunc { return func(threadId int) (err error) { + defer auditParallelRunner.ScaScansWg.Done() log.Info(clientutils.GetLogMsgPrefix(threadId, false)+"Running SCA scan for", scan.Target, "vulnerable dependencies in", scan.Target, "directory...") - var xrayErr error - defer func() { - if xrayErr == nil { - // We Sca waitGroup as done only when we have no errors. If we have errors we mark it done in the error's handler function - auditParallelRunner.ScaScansWg.Done() - } - }() // Scan the dependency tree. scanResults, xrayErr := runScaWithTech(scan.Technology, auditParams, serverDetails, *treeResult.FlatTree, treeResult.FullDepTrees) if xrayErr != nil { diff --git a/commands/curation/curationaudit.go b/commands/curation/curationaudit.go index 319d32f2..a70cb7d6 100644 --- a/commands/curation/curationaudit.go +++ b/commands/curation/curationaudit.go @@ -67,7 +67,7 @@ const ( MinArtiPassThroughSupport = "7.82.0" MinArtiGolangSupport = "7.87.0" MinArtiNuGetSupport = "7.93.0" - MinXrayPassTHroughSupport = "3.92.0" + MinXrayPassThroughSupport = "3.92.0" ) var CurationOutputFormats = []string{string(outFormat.Table), string(outFormat.Json)} @@ -94,17 +94,17 @@ func (ca *CurationAuditCommand) checkSupportByVersionOrEnv(tech techutils.Techno } else if err != nil { log.Error(err) } - artiVersion, serverDetails, err := ca.getRtVersionAndServiceDetails(tech) + artiVersion, err := ca.getRtVersion(tech) if err != nil { return false, err } - _, xrayVersion, err := xray.CreateXrayServiceManagerAndGetVersion(serverDetails) + xrayVersion, err := ca.getXrayVersion() if err != nil { return false, err } - xrayVersionErr := clientutils.ValidateMinimumVersion(clientutils.Xray, xrayVersion, MinXrayPassTHroughSupport) + xrayVersionErr := clientutils.ValidateMinimumVersion(clientutils.Xray, xrayVersion, MinXrayPassThroughSupport) rtVersionErr := clientutils.ValidateMinimumVersion(clientutils.Artifactory, artiVersion, minArtiVersion) if xrayVersionErr != nil || rtVersionErr != nil { return false, errors.Join(xrayVersionErr, rtVersionErr) @@ -112,16 +112,32 @@ func (ca *CurationAuditCommand) checkSupportByVersionOrEnv(tech techutils.Techno return true, nil } -func (ca *CurationAuditCommand) getRtVersionAndServiceDetails(tech techutils.Technology) (string, *config.ServerDetails, error) { - rtManager, serveDetails, err := ca.getRtManagerAndAuth(tech) +func (ca *CurationAuditCommand) getRtVersion(tech techutils.Technology) (string, error) { + rtManager, _, err := ca.getRtManagerAndAuth(tech) if err != nil { - return "", nil, err + return "", err } rtVersion, err := rtManager.GetVersion() if err != nil { - return "", nil, err + return "", err } - return rtVersion, serveDetails, err + return rtVersion, err +} + +func (ca *CurationAuditCommand) getXrayVersion() (string, error) { + serverDetails, err := ca.ServerDetails() + if err != nil { + return "", err + } + xrayManager, err := xray.CreateXrayServiceManager(serverDetails) + if err != nil { + return "", err + } + xrayVersion, err := xrayManager.GetVersion() + if err != nil { + return "", err + } + return xrayVersion, nil } type ErrorsResp struct { diff --git a/commands/curation/curationaudit_test.go b/commands/curation/curationaudit_test.go index 4941ea7c..943e4957 100644 --- a/commands/curation/curationaudit_test.go +++ b/commands/curation/curationaudit_test.go @@ -18,18 +18,19 @@ import ( "sync" "testing" + biutils "github.com/jfrog/build-info-go/utils" "github.com/jfrog/gofrog/datastructures" + coreCommonTests "github.com/jfrog/jfrog-cli-core/v2/common/tests" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + testUtils "github.com/jfrog/jfrog-cli-security/tests/utils" + "github.com/jfrog/jfrog-cli-security/utils" + "github.com/jfrog/jfrog-cli-security/utils/techutils" "github.com/jfrog/jfrog-client-go/utils/io/fileutils" clienttestutils "github.com/jfrog/jfrog-client-go/utils/tests" xrayUtils "github.com/jfrog/jfrog-client-go/xray/services/utils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - - coretests "github.com/jfrog/jfrog-cli-core/v2/common/tests" - "github.com/jfrog/jfrog-cli-core/v2/utils/config" - "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" - "github.com/jfrog/jfrog-cli-security/utils" - "github.com/jfrog/jfrog-cli-security/utils/techutils" ) var TestDataDir = filepath.Join("..", "..", "tests", "testdata") @@ -409,102 +410,137 @@ func fillSyncedMap(pkgStatus []*PackageStatus) *sync.Map { func TestDoCurationAudit(t *testing.T) { tests := getTestCasesForDoCurationAudit() + basePathToTests, err := filepath.Abs(TestDataDir) + assert.NoError(t, err) + + cleanUpFlags := setCurationFlagsForTest(t) + defer cleanUpFlags() + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - // Set configuration for test - currentDir, err := os.Getwd() - assert.NoError(t, err) - configurationDir := tt.pathToTest - callback := clienttestutils.SetEnvWithCallbackAndAssert(t, coreutils.HomeDir, filepath.Join(currentDir, configurationDir)) - defer callback() - callbackCurationFlag := clienttestutils.SetEnvWithCallbackAndAssert(t, utils.CurationSupportFlag, "true") - defer callbackCurationFlag() - // Golang option to disable the use of the checksum database - callbackNoSum := clienttestutils.SetEnvWithCallbackAndAssert(t, "GOSUMDB", "off") - defer callbackNoSum() - - // Create Mock server and write configuration file + // Create Mock server mockServer, config := curationServer(t, tt.expectedBuildRequest, tt.expectedRequest, tt.requestToFail, tt.requestToError, tt.serveResources) defer mockServer.Close() - configFilePath := WriteServerDetailsConfigFileBytes(t, config.ArtifactoryUrl, configurationDir, tt.createServerWithoutCreds) - defer func() { - assert.NoError(t, fileutils.RemoveTempDir(configFilePath)) - }() - rootDir, err := os.Getwd() - assert.NoError(t, err) - - // Run pre-test command - if tt.preTestExec != "" { - callbackPreTest := clienttestutils.ChangeDirWithCallback(t, rootDir, tt.pathToPreTest) - output, err := exec.Command(tt.preTestExec, tt.funcToGetGoals(t)...).CombinedOutput() - assert.NoErrorf(t, err, string(output)) - callbackPreTest() - } - - // Set the working dir for project. - callback3 := clienttestutils.ChangeDirWithCallback(t, rootDir, strings.TrimSuffix(tt.pathToTest, string(os.PathSeparator)+".jfrog")) - defer func() { - cacheFolder, err := utils.GetCurationCacheFolder() - require.NoError(t, err) - err = fileutils.RemoveTempDir(cacheFolder) - if err != nil { - // in some package manager the cache folder can be deleted only by root, in this case, test continue without failing - assert.ErrorIs(t, err, os.ErrPermission) - } - callback3() - }() - + // Create test env + cleanUp := createCurationTestEnv(t, basePathToTests, tt, config) + defer cleanUp() // Create audit command, and run it - curationCmd := NewCurationAuditCommand() - curationCmd.SetIsCurationCmd(true) - curationCmd.parallelRequests = 3 - curationCmd.SetIgnoreConfigFile(tt.shouldIgnoreConfigFile) - results := map[string]*CurationReport{} + results, err := createCurationCmdAndRun(tt) + // Validate the results if tt.requestToError == nil { - require.NoError(t, curationCmd.doCurateAudit(results)) + assert.NoError(t, err) } else { - gotError := curationCmd.doCurateAudit(results) - assert.Error(t, gotError) + assert.Error(t, err) startUrl := strings.Index(tt.expectedError, "/") assert.GreaterOrEqual(t, startUrl, 0) errMsgExpected := tt.expectedError[:startUrl] + config.ArtifactoryUrl + tt.expectedError[strings.Index(tt.expectedError, "/")+1:] - assert.EqualError(t, gotError, errMsgExpected) - } - defer func() { - if tt.cleanDependencies != nil { - assert.NoError(t, tt.cleanDependencies()) - } - }() - - // Add the mock server to the expected blocked message url - for key := range tt.expectedResp { - for index := range tt.expectedResp[key].packagesStatus { - tt.expectedResp[key].packagesStatus[index].BlockedPackageUrl = fmt.Sprintf("%s%s", - strings.TrimSuffix(config.GetArtifactoryUrl(), "/"), - tt.expectedResp[key].packagesStatus[index].BlockedPackageUrl) - } - } - // the number of packages is not deterministic for pip, as it depends on the version of the package manager. - if tt.tech == techutils.Pip { - for key := range results { - result := results[key] - result.totalNumberOfPackages = 0 - } - } - assert.Equal(t, tt.expectedResp, results) - for _, requestDone := range tt.expectedRequest { - assert.True(t, requestDone) - } - for _, requestDone := range tt.expectedBuildRequest { - assert.True(t, requestDone) + assert.EqualError(t, err, errMsgExpected) } + validateCurationResults(t, tt, results, config) }) } } +func createCurationTestEnv(t *testing.T, basePathToTests string, testCase testCase, config *config.ServerDetails) func() { + _, cleanUpHome := createTempHomeDirWithConfig(t, basePathToTests, testCase, config) + testDirPath, cleanUpTestPathDir := testUtils.CreateTestProjectEnvAndChdir(t, filepath.Join(basePathToTests, testCase.pathToProject)) + var cleanUpChdir func() + if testCase.pathToTest != "" { + // Set the test path as the current working directory + cleanUpChdir = testUtils.ChangeWDWithCallback(t, filepath.Join(testDirPath, testCase.pathToTest)) + } + // Run pre test exec + runPreTestExec(t, testDirPath, testCase) + return func() { + if cleanUpChdir != nil { + cleanUpChdir() + } + cleanUpTestPathDir() + cleanUpHome() + } +} + +func createTempHomeDirWithConfig(t *testing.T, basePathToTests string, testCase testCase, config *config.ServerDetails) (string, func()) { + tempHomeDirPath, err := fileutils.CreateTempDir() + assert.NoError(t, err) + // create .jfrog dir in temp home dir + jfrogDir := filepath.Join(tempHomeDirPath, ".jfrog") + assert.NoError(t, os.MkdirAll(jfrogDir, 0777)) + // copy .jfrog config content from test project to temp home dir + assert.NoError(t, biutils.CopyDir(filepath.Join(basePathToTests, testCase.getPathToTests(), ".jfrog"), jfrogDir, true, nil)) + // Set the home dir + callbackHomeDir := clienttestutils.SetEnvWithCallbackAndAssert(t, coreutils.HomeDir, tempHomeDirPath) + // Create the server details config file + WriteServerDetailsConfigFileBytes(t, config.ArtifactoryUrl, tempHomeDirPath, testCase.createServerWithoutCreds) + return tempHomeDirPath, func() { + callbackHomeDir() + err := fileutils.RemoveTempDir(tempHomeDirPath) + if err != nil { + // in some package manager the cache folder can be deleted only by root, in this case, test continue without failing + assert.ErrorIs(t, err, os.ErrPermission) + } + } +} + +func setCurationFlagsForTest(t *testing.T) func() { + callbackCurationFlag := clienttestutils.SetEnvWithCallbackAndAssert(t, utils.CurationSupportFlag, "true") + // Golang option to disable the use of the checksum database + callbackNoSum := clienttestutils.SetEnvWithCallbackAndAssert(t, "GOSUMDB", "off") + return func() { + callbackCurationFlag() + callbackNoSum() + } +} + +func runPreTestExec(t *testing.T, basePathToTests string, testCase testCase) { + if testCase.preTestExec == "" { + return + } + callbackPreTest := testUtils.ChangeWDWithCallback(t, filepath.Join(basePathToTests, testCase.pathToPreTest)) + output, err := exec.Command(testCase.preTestExec, testCase.funcToGetGoals(t)...).CombinedOutput() + assert.NoErrorf(t, err, string(output)) + callbackPreTest() +} + +func createCurationCmdAndRun(tt testCase) (cmdResults map[string]*CurationReport, err error) { + curationCmd := NewCurationAuditCommand() + curationCmd.SetIsCurationCmd(true) + curationCmd.parallelRequests = 3 + curationCmd.SetIgnoreConfigFile(tt.shouldIgnoreConfigFile) + cmdResults = map[string]*CurationReport{} + err = curationCmd.doCurateAudit(cmdResults) + return +} + +func validateCurationResults(t *testing.T, testCase testCase, results map[string]*CurationReport, config *config.ServerDetails) { + // Add the mock server to the expected blocked message url + for key := range testCase.expectedResp { + for index := range testCase.expectedResp[key].packagesStatus { + testCase.expectedResp[key].packagesStatus[index].BlockedPackageUrl = fmt.Sprintf("%s%s", + strings.TrimSuffix(config.GetArtifactoryUrl(), "/"), + testCase.expectedResp[key].packagesStatus[index].BlockedPackageUrl) + } + } + // the number of packages is not deterministic for pip, as it depends on the version of the package manager. + if testCase.tech == techutils.Pip { + for key := range results { + result := results[key] + result.totalNumberOfPackages = 0 + } + } + assert.Equal(t, testCase.expectedResp, results) + for _, requestDone := range testCase.expectedRequest { + assert.True(t, requestDone) + } + for _, requestDone := range testCase.expectedBuildRequest { + assert.True(t, requestDone) + } +} + type testCase struct { name string + pathToProject string pathToTest string pathToPreTest string preTestExec string @@ -517,17 +553,23 @@ type testCase struct { expectedResp map[string]*CurationReport requestToError map[string]bool expectedError string - cleanDependencies func() error tech techutils.Technology createServerWithoutCreds bool } +func (tc testCase) getPathToTests() string { + if len(tc.pathToTest) > 0 { + return filepath.Join(tc.pathToProject, tc.pathToTest) + } + return tc.pathToProject +} + func getTestCasesForDoCurationAudit() []testCase { tests := []testCase{ { name: "go tree - one blocked package", tech: techutils.Go, - pathToTest: filepath.Join(TestDataDir, "projects", "package-managers", "go", "curation-project", ".jfrog"), + pathToProject: filepath.Join("projects", "package-managers", "go", "curation-project"), createServerWithoutCreds: true, serveResources: map[string]string{ "v1.5.2.mod": filepath.Join("resources", "quote-v1.5.2.mod"), @@ -584,11 +626,10 @@ func getTestCasesForDoCurationAudit() []testCase { }, }, }, - { - name: "python tree - one blocked package", - tech: techutils.Pip, - pathToTest: filepath.Join(TestDataDir, "projects", "package-managers", "python", "pip", "pip-curation", ".jfrog"), + name: "python tree - one blocked package", + tech: techutils.Pip, + pathToProject: filepath.Join("projects", "package-managers", "python", "pip", "pip-curation"), serveResources: map[string]string{ "pip": filepath.Join("resources", "pip-resp"), "pexpect": filepath.Join("resources", "pexpect-resp"), @@ -600,7 +641,7 @@ func getTestCasesForDoCurationAudit() []testCase { "/api/pypi/pypi-remote/packages/packages/39/7b/88dbb785881c28a102619d46423cb853b46dbccc70d3ac362d99773a78ce/pexpect-4.8.0-py2.py3-none-any.whl": false, }, expectedResp: map[string]*CurationReport{ - "pip-curation": &CurationReport{packagesStatus: []*PackageStatus{ + "pip-curation": {packagesStatus: []*PackageStatus{ { Action: "blocked", ParentVersion: "4.8.0", @@ -625,31 +666,28 @@ func getTestCasesForDoCurationAudit() []testCase { { name: "maven tree - one blocked package", tech: techutils.Maven, - pathToPreTest: filepath.Join(TestDataDir, "projects", "package-managers", "maven", "maven-curation", "pretest"), + pathToProject: filepath.Join("projects", "package-managers", "maven", "maven-curation"), + pathToTest: "test", + pathToPreTest: "pretest", preTestExec: "mvn", funcToGetGoals: func(t *testing.T) []string { - rootDir, err := os.Getwd() - assert.NoError(t, err) - // set the cache to test project dir, in order to fill its cache with dependencies - callbackPreTest := clienttestutils.ChangeDirWithCallback(t, rootDir, filepath.Join("..", "test")) + // We want to populate the cache with dependencies before running the tests, so that during the test only the blocked package needs to be resolved. + // The cache directory is determined by the project directory, so we need to "simulate" the cache directory when running the pretest build. + // During the test, the blocked package will be resolved from the same cache directory that was populated in the pretest build. + cleanUpTestDirChange := testUtils.ChangeWDWithCallback(t, filepath.Join("..", "test")) curationCache, err := utils.GetCurationCacheFolderByTech(techutils.Maven) - callbackPreTest() require.NoError(t, err) + cleanUpTestDirChange() return []string{"com.jfrog:maven-dep-tree:tree", "-DdepsTreeOutputFile=output", "-Dmaven.repo.local=" + curationCache} }, - pathToTest: filepath.Join(TestDataDir, "projects", "package-managers", "maven", "maven-curation", "test", ".jfrog"), expectedBuildRequest: map[string]bool{ "/api/curation/audit/maven-remote/org/webjars/npm/underscore/1.13.6/underscore-1.13.6.pom": false, }, - cleanDependencies: func() error { - return os.RemoveAll(filepath.Join(TestDataDir, "projects", "package-managers", "maven", "maven-curation", - ".jfrog", "curation", "cache", "maven", "org", "webjars", "npm")) - }, requestToFail: map[string]bool{ "/maven-remote/org/webjars/npm/underscore/1.13.6/underscore-1.13.6.jar": false, }, expectedResp: map[string]*CurationReport{ - "test:my-app:1.0.0": &CurationReport{packagesStatus: []*PackageStatus{ + "test:my-app:1.0.0": {packagesStatus: []*PackageStatus{ { Action: "blocked", ParentVersion: "1.13.6", @@ -677,7 +715,7 @@ func getTestCasesForDoCurationAudit() []testCase { { name: "npm tree - two blocked package ", tech: techutils.Npm, - pathToTest: filepath.Join(TestDataDir, "projects", "package-managers", "npm", "npm-project", ".jfrog"), + pathToProject: filepath.Join("projects", "package-managers", "npm", "npm-project"), shouldIgnoreConfigFile: true, expectedRequest: map[string]bool{ "/api/npm/npms/lightweight/-/lightweight-0.1.0.tgz": false, @@ -687,7 +725,7 @@ func getTestCasesForDoCurationAudit() []testCase { "/api/npm/npms/underscore/-/underscore-1.13.6.tgz": false, }, expectedResp: map[string]*CurationReport{ - "npm_test:1.0.0": &CurationReport{packagesStatus: []*PackageStatus{ + "npm_test:1.0.0": {packagesStatus: []*PackageStatus{ { Action: "blocked", ParentVersion: "1.13.6", @@ -713,7 +751,7 @@ func getTestCasesForDoCurationAudit() []testCase { { name: "npm tree - two blocked one error", tech: techutils.Npm, - pathToTest: filepath.Join(TestDataDir, "projects", "package-managers", "npm", "npm-project", ".jfrog"), + pathToProject: filepath.Join("projects", "package-managers", "npm", "npm-project"), shouldIgnoreConfigFile: true, expectedRequest: map[string]bool{ "/api/npm/npms/lightweight/-/lightweight-0.1.0.tgz": false, @@ -726,7 +764,7 @@ func getTestCasesForDoCurationAudit() []testCase { "/api/npm/npms/lightweight/-/lightweight-0.1.0.tgz": false, }, expectedResp: map[string]*CurationReport{ - "npm_test:1.0.0": &CurationReport{packagesStatus: []*PackageStatus{ + "npm_test:1.0.0": {packagesStatus: []*PackageStatus{ { Action: "blocked", ParentVersion: "1.13.6", @@ -753,9 +791,9 @@ func getTestCasesForDoCurationAudit() []testCase { "/api/npm/npms/lightweight/-/lightweight-0.1.0.tgz", "lightweight:0.1.0", http.StatusInternalServerError), }, { - name: "dotnet tree", - tech: techutils.Dotnet, - pathToTest: filepath.Join(TestDataDir, "projects", "package-managers", "dotnet", "dotnet-curation"), + name: "dotnet tree", + tech: techutils.Dotnet, + pathToProject: filepath.Join("projects", "package-managers", "dotnet", "dotnet-curation"), serveResources: map[string]string{ "curated-nuget": filepath.Join("resources", "feed.json"), "index.json": filepath.Join("resources", "index.json"), @@ -794,7 +832,7 @@ func getTestCasesForDoCurationAudit() []testCase { func curationServer(t *testing.T, expectedBuildRequest map[string]bool, expectedRequest map[string]bool, requestToFail map[string]bool, requestToError map[string]bool, resourceToServe map[string]string) (*httptest.Server, *config.ServerDetails) { mapLockReadWrite := sync.Mutex{} - serverMock, config, _ := coretests.CreateRtRestsMockServer(t, func(w http.ResponseWriter, r *http.Request) { + serverMock, config, _ := coreCommonTests.CreateRtRestsMockServer(t, func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodHead { mapLockReadWrite.Lock() if _, exist := expectedRequest[r.RequestURI]; exist { @@ -833,6 +871,7 @@ func curationServer(t *testing.T, expectedBuildRequest map[string]bool, expected } } }) + config.XrayUrl = config.Url + "xray/" return serverMock, config } diff --git a/commands/enrich/enrich.go b/commands/enrich/enrich.go index ec3c748e..236a9f47 100644 --- a/commands/enrich/enrich.go +++ b/commands/enrich/enrich.go @@ -156,7 +156,7 @@ func (enrichCmd *EnrichCommand) Run() (err error) { log.Info("JFrog Xray version is:", xrayVersion) - scanResults := results.NewCommandResults(utils.SBOM, xrayVersion, false, false) + scanResults := results.NewCommandResults(utils.SBOM).SetXrayVersion(xrayVersion) fileProducerConsumer := parallel.NewRunner(enrichCmd.threads, 20000, false) indexedFileProducerConsumer := parallel.NewRunner(enrichCmd.threads, 20000, false) @@ -175,7 +175,7 @@ func (enrichCmd *EnrichCommand) Run() (err error) { fileCollectingErr := fileCollectingErrorsQueue.GetError() if fileCollectingErr != nil { - scanResults.Error = errors.Join(scanResults.Error, fileCollectingErr) + scanResults.GeneralError = errors.Join(scanResults.GeneralError, fileCollectingErr) } isXml, err := isXML(scanResults.Targets) @@ -233,13 +233,13 @@ func (enrichCmd *EnrichCommand) createIndexerHandlerFunc(indexedFileProducer par // Add a new task to the second producer/consumer // which will send the indexed binary to Xray and then will store the received result. taskFunc := func(threadId int) (err error) { + logPrefix := clientutils.GetLogMsgPrefix(threadId, false) // Create a scan target for the file. targetResults := cmdResults.NewScanResults(results.ScanTarget{Target: filePath, Name: filepath.Base(filePath)}) - log.Debug(clientutils.GetLogMsgPrefix(threadId, false)+"enrich file:", targetResults.Target) + log.Debug(logPrefix, "enrich file:", targetResults.Target) fileContent, err := os.ReadFile(targetResults.Target) if err != nil { - targetResults.AddError(err) - return err + return targetResults.AddTargetError(err, false) } params := &services.XrayGraphImportParams{ SBOMInput: fileContent, @@ -251,13 +251,11 @@ func (enrichCmd *EnrichCommand) createIndexerHandlerFunc(indexedFileProducer par SetXrayVersion(xrayVersion) xrayManager, err := xray.CreateXrayServiceManager(importGraphParams.ServerDetails()) if err != nil { - targetResults.AddError(err) - return err + return targetResults.AddTargetError(fmt.Errorf("%s failed to create Xray service manager: %s", logPrefix, err.Error()), false) } scanResults, err := enrichgraph.RunImportGraphAndGetResults(importGraphParams, xrayManager) if err != nil { - targetResults.AddError(err) - return + return targetResults.AddTargetError(fmt.Errorf("%s failed to import graph: %s", logPrefix, err.Error()), false) } targetResults.NewScaScanResults(*scanResults) targetResults.Technology = techutils.Technology(scanResults.ScannedPackageType) diff --git a/commands/git/countcontributors.go b/commands/git/countcontributors.go index c135b3e9..03e3785e 100644 --- a/commands/git/countcontributors.go +++ b/commands/git/countcontributors.go @@ -279,7 +279,7 @@ func (cc *VcsCountContributors) getRepositoriesListToScan() ([]string, error) { return cc.getOwnersMatchingRepos(reposMap) } -// getOwnersMatchingRepos gets all projects and their repo map and look for thr specific owner. +// getOwnersMatchingRepos gets all projects and their repo map and look for the specific owner. func (cc *VcsCountContributors) getOwnersMatchingRepos(reposMap map[string][]string) ([]string, error) { repos := reposMap[cc.params.Owner] if len(repos) == 0 { diff --git a/commands/scan/buildscan.go b/commands/scan/buildscan.go index 30f52998..55496016 100644 --- a/commands/scan/buildscan.go +++ b/commands/scan/buildscan.go @@ -125,7 +125,7 @@ func (bsc *BuildScanCommand) runBuildScanAndPrintResults(xrayManager *xray.XrayS return false, err } - // A patch for Xray issue where it returns Base URL from the API but it is somtimes not the URL that is configured in the CLI + // A patch for Xray issue where it returns Base URL from the API but it is sometimes not the URL that is configured in the CLI // More info in https://jfrog-int.atlassian.net/browse/XRAY-77451 url, endpoint, trimerr := trimUrl(buildScanResults.MoreDetailsUrl) @@ -145,7 +145,7 @@ func (bsc *BuildScanCommand) runBuildScanAndPrintResults(xrayManager *xray.XrayS log.Info("The scan data is available at: " + buildScanResults.MoreDetailsUrl) isFailBuildResponse = buildScanResults.FailBuild - cmdResults := results.NewCommandResults(utils.Build, xrayVersion, false, false) + cmdResults := results.NewCommandResults(utils.Build).SetXrayVersion(xrayVersion) scanResults := cmdResults.NewScanResults(results.ScanTarget{Name: fmt.Sprintf("%s (%s)", params.BuildName, params.BuildNumber)}) scanResults.NewScaScanResults(services.ScanResponse{ Violations: buildScanResults.Violations, diff --git a/commands/scan/scan.go b/commands/scan/scan.go index 056464e1..02345b26 100644 --- a/commands/scan/scan.go +++ b/commands/scan/scan.go @@ -5,12 +5,13 @@ import ( "encoding/json" "errors" "fmt" - "github.com/jfrog/jfrog-cli-security/utils/xsc" "os/exec" "path/filepath" "regexp" "strings" + "github.com/jfrog/jfrog-cli-security/utils/xsc" + "golang.org/x/exp/maps" "golang.org/x/exp/slices" @@ -40,6 +41,7 @@ import ( ioUtils "github.com/jfrog/jfrog-client-go/utils/io" "github.com/jfrog/jfrog-client-go/utils/io/fileutils" "github.com/jfrog/jfrog-client-go/utils/log" + xrayClient "github.com/jfrog/jfrog-client-go/xray" "github.com/jfrog/jfrog-client-go/xray/services" ) @@ -229,125 +231,140 @@ func (scanCmd *ScanCommand) RunAndRecordResults(cmdType utils.CommandType, recor } } }() - xrayManager, xrayVersion, err := xray.CreateXrayServiceManagerAndGetVersion(scanCmd.serverDetails) - if err != nil { - return err + + cmdResults := scanCmd.RunScan(cmdType) + + if scanCmd.progress != nil { + if err = scanCmd.progress.Quit(); err != nil { + return errors.Join(err, cmdResults.GetErrors()) + } } - entitledForJas, err := jas.IsEntitledForJas(xrayManager, xrayVersion) - if err != nil { - return err + + if err = output.NewResultsWriter(cmdResults). + SetOutputFormat(scanCmd.outputFormat). + SetHasViolationContext(scanCmd.hasViolationContext()). + SetIncludeVulnerabilities(scanCmd.includeVulnerabilities). + SetIncludeLicenses(scanCmd.includeLicenses). + SetPrintExtendedTable(scanCmd.printExtendedTable). + SetIsMultipleRootProject(cmdResults.HasMultipleTargets()). + PrintScanResults(); err != nil { + return errors.Join(err, cmdResults.GetErrors()) + } + + if err = recordResFunc(cmdResults); err != nil { + cmdResults.AddGeneralError(fmt.Errorf("failed to record results: %s", err.Error())) + } + if err = cmdResults.GetErrors(); err != nil { + return + } + // If includeVulnerabilities is false it means that context was provided, so we need to check for build violations. + // If user provided --fail=false, don't fail the build. + if scanCmd.fail && !scanCmd.includeVulnerabilities { + if results.CheckIfFailBuild(cmdResults.GetScaScansXrayResults()) { + return results.NewFailBuildError() + } } + log.Info("Scan completed successfully.") + return nil +} - cmdResults := results.NewCommandResults( - cmdType, - xrayVersion, - entitledForJas && scanCmd.commandSupportsJAS, - jas.CheckForSecretValidation(xrayManager, xrayVersion, scanCmd.validateSecrets), - ) - if scanCmd.analyticsMetricsService != nil { - cmdResults.SetMultiScanId(scanCmd.analyticsMetricsService.GetMsi()) +func (scanCmd *ScanCommand) RunScan(cmdType utils.CommandType) (cmdResults *results.SecurityCommandResults) { + xrayManager, cmdResults := initScanCmdResults(cmdType, scanCmd.serverDetails, scanCmd.analyticsMetricsService, scanCmd.bypassArchiveLimits, scanCmd.validateSecrets, scanCmd.commandSupportsJAS) + if cmdResults.GeneralError != nil { + return } + log.Info("JFrog Xray version is:", cmdResults.XrayVersion) + // First, Download (if needed) the analyzer manager in a background routine. errGroup := new(errgroup.Group) if cmdResults.EntitledForJas { - // Download (if needed) the analyzer manager in a background routine. errGroup.Go(func() error { return jas.DownloadAnalyzerManagerIfNeeded(0) }) } - - // Validate Xray minimum version for graph scan command - err = clientutils.ValidateMinimumVersion(clientutils.Xray, xrayVersion, scangraph.GraphScanMinXrayVersion) - if err != nil { - return err - } - - if scanCmd.bypassArchiveLimits { - // Validate Xray minimum version for BypassArchiveLimits flag for indexer - err = clientutils.ValidateMinimumVersion(clientutils.Xray, xrayVersion, BypassArchiveLimitsMinXrayVersion) - if err != nil { - return err - } + // Initialize the Xray Indexer + if indexerPath, indexerTempDir, cleanUp, err := initIndexer(xrayManager, cmdResults.XrayVersion); err != nil { + return cmdResults.AddGeneralError(err) + } else { + scanCmd.indexerPath = indexerPath + scanCmd.indexerTempDir = indexerTempDir + defer cleanUp() } - log.Info("JFrog Xray version is:", xrayVersion) - // First download Xray Indexer if needed - scanCmd.indexerPath, err = DownloadIndexerIfNeeded(xrayManager, xrayVersion) - if err != nil { - return err - } - // Create Temp dir for Xray Indexer - scanCmd.indexerTempDir, err = fileutils.CreateTempDir() - if err != nil { - return err - } - defer func() { - e := fileutils.RemoveTempDir(scanCmd.indexerTempDir) - if err == nil { - err = e - } - }() threads := 1 if scanCmd.threads > 1 { threads = scanCmd.threads } // Wait for the Download of the AnalyzerManager to complete. - if err = errGroup.Wait(); err != nil { - err = errors.New("failed while trying to get Analyzer Manager: " + err.Error()) + if err := errGroup.Wait(); err != nil { + cmdResults.AddGeneralError(errors.New("failed while trying to get Analyzer Manager: " + err.Error())) } fileProducerConsumer := parallel.NewRunner(threads, 20000, false) indexedFileProducerConsumer := parallel.NewRunner(threads, 20000, false) - fileCollectingErrorsQueue := clientutils.NewErrorsQueue(1) // Parallel security runner for JAS scans JasScanProducerConsumer := utils.NewSecurityParallelRunner(threads) // Start walking on the filesystem to "produce" files that match the given pattern // while the consumer uses the indexer to index those files. - scanCmd.prepareScanTasks(fileProducerConsumer, indexedFileProducerConsumer, &JasScanProducerConsumer, cmdResults, fileCollectingErrorsQueue) + scanCmd.prepareScanTasks(fileProducerConsumer, indexedFileProducerConsumer, &JasScanProducerConsumer, cmdResults) scanCmd.performScanTasks(fileProducerConsumer, indexedFileProducerConsumer, &JasScanProducerConsumer) + return +} - // Handle results - if scanCmd.progress != nil { - if err = scanCmd.progress.Quit(); err != nil { - return err +func initScanCmdResults(cmdType utils.CommandType, serverDetails *config.ServerDetails, analyticsMetricsService *xsc.AnalyticsMetricsService, bypassArchiveLimits, validateSecrets, useJas bool) (xrayManager *xrayClient.XrayServicesManager, cmdResults *results.SecurityCommandResults) { + cmdResults = results.NewCommandResults(cmdType) + xrayManager, xrayVersion, err := xray.CreateXrayServiceManagerAndGetVersion(serverDetails) + if err != nil { + return xrayManager, cmdResults.AddGeneralError(err) + } else { + cmdResults.SetXrayVersion(xrayVersion) + } + // Validate Xray minimum version for graph scan command + if err := clientutils.ValidateMinimumVersion(clientutils.Xray, xrayVersion, scangraph.GraphScanMinXrayVersion); err != nil { + return xrayManager, cmdResults.AddGeneralError(err) + } + if bypassArchiveLimits { + // Validate Xray minimum version for BypassArchiveLimits flag for indexer + if err := clientutils.ValidateMinimumVersion(clientutils.Xray, xrayVersion, BypassArchiveLimitsMinXrayVersion); err != nil { + return xrayManager, cmdResults.AddGeneralError(err) } } - - fileCollectingErr := fileCollectingErrorsQueue.GetError() - if fileCollectingErr != nil { - cmdResults.Error = errors.Join(cmdResults.Error, fileCollectingErr) + if entitledForJas, err := isEntitledForJas(xrayManager, xrayVersion, useJas); err != nil { + return xrayManager, cmdResults.AddGeneralError(err) + } else { + cmdResults.SetEntitledForJas(entitledForJas) + if entitledForJas { + cmdResults.SetSecretValidation(jas.CheckForSecretValidation(xrayManager, xrayVersion, validateSecrets)) + } } + if analyticsMetricsService != nil { + cmdResults.SetMultiScanId(analyticsMetricsService.GetMsi()) + } + return +} - // Wait for the Download of the AnalyzerManager to complete. - if err = errGroup.Wait(); err != nil { - err = errors.New("failed while trying to get Analyzer Manager: " + err.Error()) +func isEntitledForJas(xrayManager *xrayClient.XrayServicesManager, xrayVersion string, useJas bool) (bool, error) { + if !useJas { + // No jas scans are needed + return false, nil } + return jas.IsEntitledForJas(xrayManager, xrayVersion) +} - if err = output.NewResultsWriter(cmdResults). - SetOutputFormat(scanCmd.outputFormat). - SetHasViolationContext(scanCmd.hasViolationContext()). - SetIncludeVulnerabilities(scanCmd.includeVulnerabilities). - SetIncludeLicenses(scanCmd.includeLicenses). - SetPrintExtendedTable(scanCmd.printExtendedTable). - SetIsMultipleRootProject(cmdResults.HasMultipleTargets()). - PrintScanResults(); err != nil { +func initIndexer(xrayManager *xrayClient.XrayServicesManager, xrayVersion string) (indexerPath, indexerTempDir string, cleanUp func(), err error) { + // Download Xray Indexer if needed + if indexerPath, err = DownloadIndexerIfNeeded(xrayManager, xrayVersion); err != nil { return } - - if err = recordResFunc(cmdResults); err != nil { - return err + // Create Temp dir for Xray Indexer + if indexerTempDir, err = fileutils.CreateTempDir(); err != nil { + return } - - // If includeVulnerabilities is false it means that context was provided, so we need to check for build violations. - // If user provided --fail=false, don't fail the build. - if scanCmd.fail && !scanCmd.includeVulnerabilities { - if results.CheckIfFailBuild(cmdResults.GetScaScansXrayResults()) { - return results.NewFailBuildError() + cleanUp = func() { + e := fileutils.RemoveTempDir(indexerTempDir) + if err == nil { + err = e } } - if cmdResults.GetErrors() != nil { - return errorutils.CheckError(cmdResults.GetErrors()) - } - log.Info("Scan completed successfully.") - return nil + return } func NewScanCommand() *ScanCommand { @@ -358,7 +375,7 @@ func (scanCmd *ScanCommand) CommandName() string { return "xr_scan" } -func (scanCmd *ScanCommand) prepareScanTasks(fileProducer, indexedFileProducer parallel.Runner, jasFileProducerConsumer *utils.SecurityParallelRunner, cmdResults *results.SecurityCommandResults, fileCollectingErrorsQueue *clientutils.ErrorsQueue) { +func (scanCmd *ScanCommand) prepareScanTasks(fileProducer, indexedFileProducer parallel.Runner, jasFileProducerConsumer *utils.SecurityParallelRunner, cmdResults *results.SecurityCommandResults) { go func() { defer fileProducer.Done() // Iterate over file-spec groups and produce indexing tasks. @@ -367,11 +384,9 @@ func (scanCmd *ScanCommand) prepareScanTasks(fileProducer, indexedFileProducer p for i := range specFiles { artifactHandlerFunc := scanCmd.createIndexerHandlerFunc(&specFiles[i], cmdResults, indexedFileProducer, jasFileProducerConsumer) taskHandler := getAddTaskToProducerFunc(fileProducer, artifactHandlerFunc) - - err := collectFilesForIndexing(specFiles[i], taskHandler) - if err != nil { - log.Error(err) - fileCollectingErrorsQueue.AddError(err) + if generalError := collectFilesForIndexing(specFiles[i], taskHandler); generalError != nil { + log.Error(generalError) + cmdResults.AddGeneralError(generalError) } } }() @@ -387,28 +402,27 @@ func (scanCmd *ScanCommand) getBinaryTargetName(binaryPath string) string { func (scanCmd *ScanCommand) createIndexerHandlerFunc(file *spec.File, cmdResults *results.SecurityCommandResults, indexedFileProducer parallel.Runner, jasFileProducerConsumer *utils.SecurityParallelRunner) FileContext { return func(filePath string) parallel.TaskFunc { return func(threadId int) (err error) { - logMsgPrefix := clientutils.GetLogMsgPrefix(threadId, false) // Create a scan target for the file. targetResults := cmdResults.NewScanResults(results.ScanTarget{Target: filePath, Name: scanCmd.getBinaryTargetName(filePath)}) - log.Info(logMsgPrefix+"Indexing file:", targetResults.Target) + log.Info(clientutils.GetLogMsgPrefix(threadId, false), "Indexing file:", targetResults.Target) if scanCmd.progress != nil { scanCmd.progress.SetHeadlineMsg("Indexing file: " + targetResults.Name + " 🗄") } // Index the file and get the dependencies graph. graph, err := scanCmd.indexFile(targetResults.Target) if err != nil { - targetResults.AddError(err) - return err + return targetResults.AddTargetError(err, false) } // In case of empty graph returned by the indexer, // for instance due to unsupported file format, continue without sending a // graph request to Xray. if graph.Id == "" { - return nil + return } // Add a new task to the second producer/consumer // which will send the indexed binary to Xray and then will store the received result. taskFunc := func(scanThreadId int) (err error) { + scanLogPrefix := clientutils.GetLogMsgPrefix(scanThreadId, false) params := &services.XrayGraphScanParams{ BinaryGraph: graph, RepoPath: getXrayRepoPathFromTarget(file.Target), @@ -430,13 +444,11 @@ func (scanCmd *ScanCommand) createIndexerHandlerFunc(file *spec.File, cmdResults SetSeverityLevel(scanCmd.minSeverityFilter.String()) xrayManager, err := xray.CreateXrayServiceManager(scanGraphParams.ServerDetails()) if err != nil { - return err + return targetResults.AddTargetError(fmt.Errorf("%s failed to create Xray service manager: %s", scanLogPrefix, err.Error()), false) } graphScanResults, err := scangraph.RunScanGraphAndGetResults(scanGraphParams, xrayManager) if err != nil { - log.Error(fmt.Sprintf("%s sca scanning '%s' failed with error: %s", clientutils.GetLogMsgPrefix(scanThreadId, false), graph.Id, err.Error())) - targetResults.AddError(err) - return + return targetResults.AddTargetError(fmt.Errorf("%s sca scanning '%s' failed with error: %s", scanLogPrefix, graph.Id, err.Error()), false) } else { targetResults.NewScaScanResults(*graphScanResults) targetResults.Technology = techutils.Technology(graphScanResults.ScannedPackageType) @@ -444,14 +456,17 @@ func (scanCmd *ScanCommand) createIndexerHandlerFunc(file *spec.File, cmdResults if !cmdResults.EntitledForJas { return } - // Run Jas scans - scanner, err := getJasScanner(cmdResults.MultiScanId, scanCmd.serverDetails, targetResults, cmdResults.SecretValidation, scanCmd.minSeverityFilter) + module, err := getJasModule(targetResults) if err != nil { - return err + return targetResults.AddTargetError(fmt.Errorf("%s jas scanning failed with error: %s", scanLogPrefix, err.Error()), false) } - module, err := getJasModule(targetResults) + // Run Jas scans + scanner, err := jas.CreateJasScanner(scanCmd.serverDetails, cmdResults.SecretValidation, scanCmd.minSeverityFilter, jas.GetAnalyzerManagerXscEnvVars(cmdResults.MultiScanId, targetResults.GetTechnologies()...)) if err != nil { - return err + return targetResults.AddTargetError(fmt.Errorf("failed to create jas scanner: %s", err.Error()), false) + } else if scanner == nil { + log.Debug("Jas scanner was not created, skipping advance security scans...") + return } jasParams := runner.JasRunnerParams{ Runner: jasFileProducerConsumer, @@ -464,13 +479,8 @@ func (scanCmd *ScanCommand) createIndexerHandlerFunc(file *spec.File, cmdResults ApplicableScanType: applicability.ApplicabilityDockerScanScanType, ScanResults: targetResults, } - err = runner.AddJasScannersTasks(jasParams) - if err != nil { - log.Error(fmt.Sprintf("%s jas scanning failed with error: %s", clientutils.GetLogMsgPrefix(scanThreadId, false), err.Error())) - targetResults.AddError(err) - } else if scanner == nil { - log.Debug(fmt.Sprintf("Jas scanner was not created for %s, skipping Jas scans", filePath)) - return nil + if generalError := runner.AddJasScannersTasks(jasParams); generalError != nil { + return targetResults.AddTargetError(fmt.Errorf("%s failed to add Jas scan tasks: %s", scanLogPrefix, generalError.Error()), false) } return } @@ -480,21 +490,9 @@ func (scanCmd *ScanCommand) createIndexerHandlerFunc(file *spec.File, cmdResults } } -func getJasScanner(multiScanId string, serverDetails *config.ServerDetails, targetResults *results.TargetResults, secretValidation bool, minSeverity severityutils.Severity) (*jas.JasScanner, error) { - scanner, err := jas.CreateJasScanner(serverDetails, secretValidation, minSeverity, jas.GetAnalyzerManagerXscEnvVars(multiScanId, targetResults.GetTechnologies()...)) - if err != nil { - log.Error(fmt.Sprintf("failed to create jas scanner: %s", err.Error())) - targetResults.AddError(err) - return nil, err - } - return scanner, nil -} - func getJasModule(targetResults *results.TargetResults) (jfrogappsconfig.Module, error) { jfrogAppsConfig, err := jas.CreateJFrogAppsConfig([]string{targetResults.Target}) if err != nil { - log.Error(fmt.Sprintf("failed to create JFrogAppsConfig module: %s", err.Error())) - targetResults.AddError(err) return jfrogappsconfig.Module{}, err } return jfrogAppsConfig.Modules[0], nil @@ -523,20 +521,17 @@ func (scanCmd *ScanCommand) performScanTasks(fileConsumer parallel.Runner, index jasScanProducerConsumer.Runner.Run() } -func collectFilesForIndexing(fileData spec.File, dataHandlerFunc indexFileHandlerFunc) error { - +func collectFilesForIndexing(fileData spec.File, dataHandlerFunc indexFileHandlerFunc) (generalError error) { fileData.Pattern = clientutils.ReplaceTildeWithUserHome(fileData.Pattern) patternType := fileData.GetPatternType() - rootPath, err := fspatterns.GetRootPath(fileData.Pattern, fileData.Target, "", patternType, false) - if err != nil { - return err + rootPath, generalError := fspatterns.GetRootPath(fileData.Pattern, fileData.Target, "", patternType, false) + if generalError != nil { + return generalError } - - isDir, err := fileutils.IsDirExists(rootPath, false) - if err != nil { - return err + isDir, generalError := fileutils.IsDirExists(rootPath, false) + if generalError != nil { + return generalError } - // If the path is a single file, index it and return if !isDir { dataHandlerFunc(rootPath) @@ -546,29 +541,29 @@ func collectFilesForIndexing(fileData spec.File, dataHandlerFunc indexFileHandle return collectPatternMatchingFiles(fileData, rootPath, dataHandlerFunc) } -func collectPatternMatchingFiles(fileData spec.File, rootPath string, dataHandlerFunc indexFileHandlerFunc) error { - fileParams, err := fileData.ToCommonParams() - if err != nil { - return err +func collectPatternMatchingFiles(fileData spec.File, rootPath string, dataHandlerFunc indexFileHandlerFunc) (generalError error) { + fileParams, generalError := fileData.ToCommonParams() + if generalError != nil { + return generalError } excludePathPattern := fspatterns.PrepareExcludePathPattern(fileParams.Exclusions, fileParams.GetPatternType(), fileParams.IsRecursive()) - patternRegex, err := regexp.Compile(fileData.Pattern) - if errorutils.CheckError(err) != nil { - return err + patternRegex, generalError := regexp.Compile(fileData.Pattern) + if errorutils.CheckError(generalError) != nil { + return generalError } - recursive, err := fileData.IsRecursive(true) - if err != nil { - return err + recursive, generalError := fileData.IsRecursive(true) + if generalError != nil { + return generalError } - paths, err := fspatterns.ListFiles(rootPath, recursive, false, false, false, excludePathPattern) - if err != nil { - return err + paths, generalError := fspatterns.ListFiles(rootPath, recursive, false, false, false, excludePathPattern) + if generalError != nil { + return generalError } for _, path := range paths { - matches, isDir, err := fspatterns.SearchPatterns(path, false, false, patternRegex) - if err != nil { - return err + matches, isDir, generalError := fspatterns.SearchPatterns(path, false, false, patternRegex) + if generalError != nil { + return generalError } // Because paths should contain all files and directories (walks recursively) we can ignore dirs, as only files relevance for indexing. if isDir { diff --git a/enrich_test.go b/enrich_test.go index 77d141ba..1462020b 100644 --- a/enrich_test.go +++ b/enrich_test.go @@ -1,27 +1,20 @@ package main import ( + "path/filepath" + "testing" + "github.com/jfrog/jfrog-cli-security/commands/enrich/enrichgraph" securityTests "github.com/jfrog/jfrog-cli-security/tests" securityTestUtils "github.com/jfrog/jfrog-cli-security/tests/utils" + "github.com/jfrog/jfrog-cli-security/tests/utils/integration" + securityIntegrationTestUtils "github.com/jfrog/jfrog-cli-security/tests/utils/integration" "github.com/stretchr/testify/assert" - "path/filepath" - "testing" ) -func testVulns(t *testing.T, vulns []struct { - BomRef string - Id string -}) { - for _, vuln := range vulns { - assert.NotEqual(t, vuln.BomRef, nil) - assert.NotEqual(t, vuln.Id, nil) - } -} - func TestXrayEnrichSbomOutput(t *testing.T) { - securityTestUtils.InitSecurityTest(t, enrichgraph.EnrichMinimumVersionXray) - securityTestUtils.CreateJfrogHomeConfig(t, true) + integration.InitEnrichTest(t, enrichgraph.EnrichMinimumVersionXray) + securityIntegrationTestUtils.CreateJfrogHomeConfig(t, true) defer securityTestUtils.CleanTestsHomeEnv() testCases := []struct { name string @@ -40,12 +33,12 @@ func TestXrayEnrichSbomOutput(t *testing.T) { } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - inputPath := filepath.Join(filepath.FromSlash(securityTestUtils.GetTestResourcesPath()), "other", "enrich", tc.inputPath) + inputPath := filepath.Join(filepath.FromSlash(securityTests.GetTestResourcesPath()), "other", "enrich", tc.inputPath) output := securityTests.PlatformCli.RunCliCmdWithOutput(t, "sbom-enrich", inputPath) if tc.isXml { enrichedSbom := securityTestUtils.UnmarshalXML(t, output) assert.Greater(t, len(enrichedSbom.Vulnerabilities.Vulnerability), 0) - testVulns(t, []struct { + testVulnerabilities(t, []struct { BomRef string Id string }(enrichedSbom.Vulnerabilities.Vulnerability)) @@ -53,7 +46,7 @@ func TestXrayEnrichSbomOutput(t *testing.T) { } else { enrichedSbom := securityTestUtils.UnmarshalJson(t, output) assert.Greater(t, len(enrichedSbom.Vulnerability), 0) - testVulns(t, []struct { + testVulnerabilities(t, []struct { BomRef string Id string }(enrichedSbom.Vulnerability)) @@ -63,3 +56,13 @@ func TestXrayEnrichSbomOutput(t *testing.T) { }) } } + +func testVulnerabilities(t *testing.T, vulnerabilities []struct { + BomRef string + Id string +}) { + for _, vulnerability := range vulnerabilities { + assert.NotEqual(t, vulnerability.BomRef, nil) + assert.NotEqual(t, vulnerability.Id, nil) + } +} diff --git a/git_test.go b/git_test.go index 0c09df47..81969ee8 100644 --- a/git_test.go +++ b/git_test.go @@ -1,14 +1,17 @@ package main import ( + "testing" + "github.com/jfrog/jfrog-cli-security/commands/git" securityTests "github.com/jfrog/jfrog-cli-security/tests" + "github.com/jfrog/jfrog-cli-security/tests/utils/integration" "github.com/jfrog/jfrog-client-go/utils/tests" "github.com/stretchr/testify/assert" - "testing" ) func TestCountContributorsFlags(t *testing.T) { + integration.InitGitTest(t) err := securityTests.PlatformCli.WithoutCredentials().Exec("git", "count-contributors", "--token", "token", "--owner", "owner", "--scm-api-url", "url") assert.EqualError(t, err, "Mandatory flag 'scm-type' is missing") err = securityTests.PlatformCli.WithoutCredentials().Exec("git", "cc", "--scm-type", "github", "--owner", "owner", "--scm-api-url", "url") diff --git a/jas/common.go b/jas/common.go index 2154f3c1..8c060fd2 100644 --- a/jas/common.go +++ b/jas/common.go @@ -82,7 +82,6 @@ func CreateJasScanner(serverDetails *config.ServerDetails, validateSecrets bool, func getJasEnvVars(serverDetails *config.ServerDetails, validateSecrets bool, vars map[string]string) (map[string]string, error) { amBasicVars, err := GetAnalyzerManagerEnvVariables(serverDetails) - log.Debug("Adding the following environment variables to the analyzer manager", amBasicVars) if err != nil { return nil, err } diff --git a/jas/runner/jasrunner.go b/jas/runner/jasrunner.go index c2694d0e..8128e281 100644 --- a/jas/runner/jasrunner.go +++ b/jas/runner/jasrunner.go @@ -45,36 +45,36 @@ type JasRunnerParams struct { TargetOutputDir string } -func AddJasScannersTasks(params JasRunnerParams) (err error) { +func AddJasScannersTasks(params JasRunnerParams) (generalError error) { // Set the analyzer manager executable path. - if params.Scanner.AnalyzerManager.AnalyzerManagerFullPath, err = jas.GetAnalyzerManagerExecutable(); err != nil { - return + if params.Scanner.AnalyzerManager.AnalyzerManagerFullPath, generalError = jas.GetAnalyzerManagerExecutable(); generalError != nil { + return fmt.Errorf("failed to set analyzer manager executable path: %s", generalError.Error()) } // For docker scan we support only secrets and contextual scans. runAllScanners := false if params.ApplicableScanType == applicability.ApplicabilityScannerType || params.SecretsScanType == secrets.SecretsScannerType { runAllScanners = true } - if err = addJasScanTaskForModuleIfNeeded(params, utils.ContextualAnalysisScan, runContextualScan(params.Runner, params.Scanner, params.ScanResults, params.Module, params.DirectDependencies, params.ThirdPartyApplicabilityScan, params.ApplicableScanType, params.TargetOutputDir)); err != nil { + if generalError = addJasScanTaskForModuleIfNeeded(params, utils.ContextualAnalysisScan, runContextualScan(params.Runner, params.Scanner, params.ScanResults, params.Module, params.DirectDependencies, params.ThirdPartyApplicabilityScan, params.ApplicableScanType, params.TargetOutputDir)); generalError != nil { return } if params.ThirdPartyApplicabilityScan { // Don't execute other scanners when scanning third party dependencies. return } - if err = addJasScanTaskForModuleIfNeeded(params, utils.SecretsScan, runSecretsScan(params.Runner, params.Scanner, params.ScanResults.JasResults, params.Module, params.SecretsScanType, params.TargetOutputDir)); err != nil { + if generalError = addJasScanTaskForModuleIfNeeded(params, utils.SecretsScan, runSecretsScan(params.Runner, params.Scanner, params.ScanResults.JasResults, params.Module, params.SecretsScanType, params.TargetOutputDir)); generalError != nil { return } if !runAllScanners { return } - if err = addJasScanTaskForModuleIfNeeded(params, utils.IacScan, runIacScan(params.Runner, params.Scanner, params.ScanResults.JasResults, params.Module, params.TargetOutputDir)); err != nil { + if generalError = addJasScanTaskForModuleIfNeeded(params, utils.IacScan, runIacScan(params.Runner, params.Scanner, params.ScanResults.JasResults, params.Module, params.TargetOutputDir)); generalError != nil { return } return addJasScanTaskForModuleIfNeeded(params, utils.SastScan, runSastScan(params.Runner, params.Scanner, params.ScanResults.JasResults, params.Module, params.TargetOutputDir, params.SignedDescriptions)) } -func addJasScanTaskForModuleIfNeeded(params JasRunnerParams, subScan utils.SubScanType, task parallel.TaskFunc) (err error) { +func addJasScanTaskForModuleIfNeeded(params JasRunnerParams, subScan utils.SubScanType, task parallel.TaskFunc) (generalError error) { jasType := jasutils.SubScanTypeToJasScanType(subScan) if jasType == "" { return fmt.Errorf("failed to determine Jas scan type for %s", subScan) @@ -100,7 +100,7 @@ func addJasScanTaskForModuleIfNeeded(params JasRunnerParams, subScan utils.SubSc return } if enabled { - err = addModuleJasScanTask(jasType, params.Runner, task, params.ScanResults) + generalError = addModuleJasScanTask(jasType, params.Runner, task, params.ScanResults) } else { log.Debug(fmt.Sprintf("Skipping %s scan as requested by '%s' config profile...", jasType, params.ConfigProfile.ProfileName)) } @@ -113,12 +113,12 @@ func addJasScanTaskForModuleIfNeeded(params JasRunnerParams, subScan utils.SubSc return addModuleJasScanTask(jasType, params.Runner, task, params.ScanResults) } -func addModuleJasScanTask(scanType jasutils.JasScanType, securityParallelRunner *utils.SecurityParallelRunner, task parallel.TaskFunc, scanResults *results.TargetResults) (err error) { +func addModuleJasScanTask(scanType jasutils.JasScanType, securityParallelRunner *utils.SecurityParallelRunner, task parallel.TaskFunc, scanResults *results.TargetResults) (generalError error) { securityParallelRunner.JasScannersWg.Add(1) - if _, err = securityParallelRunner.Runner.AddTaskWithError(task, func(err error) { - scanResults.AddError(err) - }); err != nil { - err = fmt.Errorf("failed to create %s scan task: %s", scanType, err.Error()) + if _, addTaskErr := securityParallelRunner.Runner.AddTaskWithError(task, func(err error) { + _ = scanResults.AddTargetError(fmt.Errorf("failed to run %s scan: %s", scanType, err.Error()), false) + }); addTaskErr != nil { + generalError = fmt.Errorf("failed to create %s scan task: %s", scanType, addTaskErr.Error()) } return } diff --git a/jas/runner/jasrunner_test.go b/jas/runner/jasrunner_test.go index cdddf403..22b906fa 100644 --- a/jas/runner/jasrunner_test.go +++ b/jas/runner/jasrunner_test.go @@ -40,7 +40,7 @@ func TestJasRunner_AnalyzerManagerNotExist(t *testing.T) { func TestJasRunner(t *testing.T) { assert.NoError(t, jas.DownloadAnalyzerManagerIfNeeded(0)) securityParallelRunnerForTest := utils.CreateSecurityParallelRunner(cliutils.Threads) - targetResults := results.NewCommandResults(utils.SourceCode, "", true, true).NewScanResults(results.ScanTarget{Target: "target", Technology: techutils.Pip}) + targetResults := results.NewCommandResults(utils.SourceCode).SetEntitledForJas(true).SetSecretValidation(true).NewScanResults(results.ScanTarget{Target: "target", Technology: techutils.Pip}) jasScanner, err := jas.CreateJasScanner(&jas.FakeServerDetails, false, "", jas.GetAnalyzerManagerXscEnvVars("", targetResults.GetTechnologies()...)) assert.NoError(t, err) @@ -55,8 +55,7 @@ func TestJasRunner(t *testing.T) { SecretsScanType: secrets.SecretsScannerType, DirectDependencies: &[]string{"issueId_1_direct_dependency", "issueId_2_direct_dependency"}, } - err = AddJasScannersTasks(testParams) - assert.NoError(t, err) + assert.NoError(t, AddJasScannersTasks(testParams)) } func TestJasRunner_AnalyzerManagerReturnsError(t *testing.T) { diff --git a/jfrogclisecurity_test.go b/jfrogclisecurity_test.go index d8be3833..b08f5c58 100644 --- a/jfrogclisecurity_test.go +++ b/jfrogclisecurity_test.go @@ -1,13 +1,12 @@ package main import ( - "flag" "fmt" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" "github.com/jfrog/jfrog-cli-core/v2/utils/log" "github.com/jfrog/jfrog-cli-security/cli" - "github.com/jfrog/jfrog-cli-security/tests/utils" + integrationUtils "github.com/jfrog/jfrog-cli-security/tests/utils/integration" configTests "github.com/jfrog/jfrog-cli-security/tests" @@ -36,17 +35,17 @@ func setupIntegrationTests() { os.Exit(1) } // General - flag.Parse() + configTests.InitTestFlags() log.SetDefaultLogger() // Init - utils.InitTestCliDetails(cli.GetJfrogCliSecurityApp()) - utils.AuthenticateArtifactory() - utils.AuthenticateXsc() - utils.CreateRequiredRepositories() + integrationUtils.InitTestCliDetails(cli.GetJfrogCliSecurityApp()) + integrationUtils.AuthenticateArtifactory() + integrationUtils.AuthenticateXsc() + integrationUtils.CreateRequiredRepositories() } func tearDownIntegrationTests() { // Important - Virtual repositories must be deleted first - utils.DeleteRepos(configTests.CreatedVirtualRepositories) - utils.DeleteRepos(configTests.CreatedNonVirtualRepositories) + integrationUtils.DeleteRepos(configTests.CreatedVirtualRepositories) + integrationUtils.DeleteRepos(configTests.CreatedNonVirtualRepositories) } diff --git a/scans_test.go b/scans_test.go index 3668326c..818bbb9c 100644 --- a/scans_test.go +++ b/scans_test.go @@ -5,7 +5,6 @@ import ( "fmt" "net/http" "net/http/httptest" - "os" "path" "path/filepath" "strings" @@ -15,16 +14,14 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - biutils "github.com/jfrog/build-info-go/utils" - "github.com/jfrog/jfrog-cli-security/utils/formats" - "github.com/jfrog/jfrog-cli-security/utils/jasutils" - "github.com/jfrog/jfrog-cli-security/utils/validations" - - "github.com/jfrog/jfrog-cli-security/cli" "github.com/jfrog/jfrog-cli-security/commands/curation" "github.com/jfrog/jfrog-cli-security/commands/scan" securityTests "github.com/jfrog/jfrog-cli-security/tests" securityTestUtils "github.com/jfrog/jfrog-cli-security/tests/utils" + "github.com/jfrog/jfrog-cli-security/tests/utils/integration" + "github.com/jfrog/jfrog-cli-security/utils/formats" + "github.com/jfrog/jfrog-cli-security/utils/jasutils" + "github.com/jfrog/jfrog-cli-security/utils/validations" "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/container" containerUtils "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils/container" @@ -83,8 +80,8 @@ func TestXrayBinaryScanSimpleJsonWithProgress(t *testing.T) { } func testXrayBinaryScan(t *testing.T, format string, withViolation bool) string { - securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion) - binariesPath := filepath.Join(filepath.FromSlash(securityTestUtils.GetTestResourcesPath()), "projects", "binaries", "*") + integration.InitScanTest(t, scangraph.GraphScanMinXrayVersion) + binariesPath := filepath.Join(filepath.FromSlash(securityTests.GetTestResourcesPath()), "projects", "binaries", "*") args := []string{"scan", binariesPath, "--licenses", "--format=" + format} if withViolation { watchName, deleteWatch := securityTestUtils.CreateTestWatch(t, "audit-policy", "audit-watch", xrayUtils.High) @@ -96,10 +93,10 @@ func testXrayBinaryScan(t *testing.T, format string, withViolation bool) string } func TestXrayBinaryScanWithBypassArchiveLimits(t *testing.T) { - securityTestUtils.InitSecurityTest(t, scan.BypassArchiveLimitsMinXrayVersion) + integration.InitScanTest(t, scan.BypassArchiveLimitsMinXrayVersion) unsetEnv := clientTestUtils.SetEnvWithCallbackAndAssert(t, "JF_INDEXER_COMPRESS_MAXENTITIES", "10") defer unsetEnv() - binariesPath := filepath.Join(filepath.FromSlash(securityTestUtils.GetTestResourcesPath()), "projects", "binaries", "*") + binariesPath := filepath.Join(filepath.FromSlash(securityTests.GetTestResourcesPath()), "projects", "binaries", "*") scanArgs := []string{"scan", binariesPath, "--format=json", "--licenses"} // Run without bypass flag and expect scan to fail err := securityTests.PlatformCli.Exec(scanArgs...) @@ -124,8 +121,8 @@ func TestDockerScanWithProgressBar(t *testing.T) { } func TestDockerScanWithTokenValidation(t *testing.T) { - securityTestUtils.InitSecurityTest(t, jasutils.DynamicTokenValidationMinXrayVersion) - testCli, cleanup := initNativeDockerWithXrayTest(t) + integration.InitScanTest(t, jasutils.DynamicTokenValidationMinXrayVersion) + testCli, cleanup := integration.InitNativeDockerTest(t) defer cleanup() // #nosec G101 -- Image with dummy token for tests tokensImageToScan := "srmishj/inactive_tokens:latest" @@ -133,7 +130,8 @@ func TestDockerScanWithTokenValidation(t *testing.T) { } func TestDockerScan(t *testing.T) { - testCli, cleanup := initNativeDockerWithXrayTest(t) + integration.InitScanTest(t, "") + testCli, cleanup := integration.InitNativeDockerTest(t) defer cleanup() watchName, deleteWatch := securityTestUtils.CreateTestWatch(t, "docker-policy", "docker-watch", xrayUtils.Low) @@ -150,21 +148,10 @@ func TestDockerScan(t *testing.T) { runDockerScan(t, testCli, imageName, watchName, 3, 3, 3, 0, false) } - // On Xray 3.40.3 there is a bug whereby xray fails to scan docker image with 0 vulnerabilities, - // So we skip it for now till the next version will be released - securityTestUtils.ValidateXrayVersion(t, "3.41.0") - // Image with 0 vulnerabilities runDockerScan(t, testCli, "busybox:1.35", "", 0, 0, 0, 0, false) } -func initNativeDockerWithXrayTest(t *testing.T) (mockCli *coreTests.JfrogCli, cleanUp func()) { - if !*securityTests.TestDockerScan || !*securityTests.TestSecurity { - t.Skip("Skipping Docker scan test. To run Xray Docker test add the '-test.dockerScan=true' and '-test.security=true' options.") - } - return securityTestUtils.InitTestWithMockCommandOrParams(t, false, cli.DockerScanMockCommand) -} - func runDockerScan(t *testing.T, testCli *coreTests.JfrogCli, imageName, watchName string, minViolations, minVulnerabilities, minLicenses int, minInactives int, validateSecrets bool) { // Pull image from docker repo imageTag := path.Join(*securityTests.ContainerRegistry, securityTests.DockerVirtualRepo, imageName) @@ -202,7 +189,8 @@ func runDockerScan(t *testing.T, testCli *coreTests.JfrogCli, imageName, watchNa // JAS docker scan tests func TestAdvancedSecurityDockerScan(t *testing.T) { - testCli, cleanup := initNativeDockerWithXrayTest(t) + integration.InitScanTest(t, "") + testCli, cleanup := integration.InitNativeDockerTest(t) defer cleanup() runAdvancedSecurityDockerScan(t, testCli, "jfrog/demo-security:latest") } @@ -246,17 +234,10 @@ func verifyAdvancedSecurityScanResults(t *testing.T, content string) { // Curation tests func TestCurationAudit(t *testing.T) { - securityTestUtils.InitSecurityTest(t, "") - tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) - defer createTempDirCallback() - multiProject := filepath.Join(filepath.FromSlash(securityTestUtils.GetTestResourcesPath()), "projects", "package-managers", "npm") - assert.NoError(t, biutils.CopyDir(multiProject, tempDirPath, true, nil)) - rootDir, err := os.Getwd() - require.NoError(t, err) - defer func() { - assert.NoError(t, os.Chdir(rootDir)) - }() - require.NoError(t, os.Chdir(filepath.Join(tempDirPath, "npm"))) + integration.InitCurationTest(t) + tempDirPath, cleanUp := securityTestUtils.CreateTestProjectEnvAndChdir(t, filepath.Join(filepath.FromSlash(securityTests.GetTestResourcesPath()), "projects", "package-managers", "npm")) + defer cleanUp() + expectedRequest := map[string]bool{ "/api/npm/npms/json/-/json-9.0.6.tgz": false, "/api/npm/npms/xml/-/xml-1.0.1.tgz": false, diff --git a/tests/config.go b/tests/config.go index 5f6e8e3f..8461f9e5 100644 --- a/tests/config.go +++ b/tests/config.go @@ -2,11 +2,13 @@ package tests import ( "flag" + "os" "github.com/jfrog/jfrog-cli-core/v2/plugins/components" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-client-go/auth" "github.com/jfrog/jfrog-client-go/utils/io/httputils" + "github.com/jfrog/jfrog-client-go/utils/log" coreTests "github.com/jfrog/jfrog-cli-core/v2/utils/tests" ) @@ -23,47 +25,125 @@ var ( RtAuth auth.ServiceDetails RtHttpDetails httputils.HttpClientDetails - PlatformCli *coreTests.JfrogCli - + PlatformCli *coreTests.JfrogCli TestApplication *components.App timestampAdded bool + RunAllTests bool ) // Test flags var ( - TestSecurity *bool - TestDockerScan *bool + TestUnit *bool + TestArtifactory *bool + TestXray *bool + TestXsc *bool + TestScan *bool + TestDockerScan *bool + TestCuration *bool + TestEnrich *bool + TestGit *bool + + TestAuditGeneral *bool + TestAuditJas *bool + TestAuditJavaScript *bool + TestAuditJava *bool + TestAuditCTypes *bool + TestAuditGo *bool + TestAuditPython *bool + + JfrogUrl *string + JfrogUser *string + JfrogPassword *string + JfrogAccessToken *string - JfrogUrl *string - JfrogUser *string - JfrogPassword *string JfrogSshKeyPath *string JfrogSshPassphrase *string - JfrogAccessToken *string + ContainerRegistry *string + ciRunId *string +) + +func getTestUrlDefaultValue() string { + if os.Getenv(TestJfrogUrlEnvVar) != "" { + return os.Getenv(TestJfrogUrlEnvVar) + } + return "http://localhost:8083/" +} - ContainerRegistry *string +func getTestUserDefaultValue() string { + if os.Getenv(TestJfrogUserEnvVar) != "" { + return os.Getenv(TestJfrogUserEnvVar) + } + return "admin" +} - HideUnitTestLog *bool - SkipUnitTests *bool - ciRunId *string -) +func getTestPasswordDefaultValue() string { + if os.Getenv(TestJfrogPasswordEnvVar) != "" { + return os.Getenv(TestJfrogPasswordEnvVar) + } + return "password" +} func init() { - TestSecurity = flag.Bool("test.security", true, "Test Security") - TestDockerScan = flag.Bool("test.dockerScan", false, "Test Docker scan") + TestUnit = flag.Bool("test.unit", false, "Run unit tests") + TestArtifactory = flag.Bool("test.artifactory", false, "Run Artifactory integration tests") + TestXsc = flag.Bool("test.xsc", false, "Run XSC integration tests") + TestXray = flag.Bool("test.xray", false, "Run Xray commands integration tests") + TestScan = flag.Bool("test.scan", false, "Run Other scan commands integration tests") + TestDockerScan = flag.Bool("test.dockerScan", false, "Run Docker scan command integration tests") + TestCuration = flag.Bool("test.curation", false, "Run Curation command integration tests") + TestEnrich = flag.Bool("test.enrich", false, "Run Enrich command integration tests") + TestGit = flag.Bool("test.git", false, "Run Git commands integration tests") + + TestAuditGeneral = flag.Bool("test.audit", false, "Run general (Detection, NoTech, MultiTech...) audit integration tests") + TestAuditJas = flag.Bool("test.audit.Jas", false, "Run Jas audit integration tests") + TestAuditJavaScript = flag.Bool("test.audit.JavaScript", false, "Run JavaScript technologies (Npm, Pnpm, Yarn) audit integration tests") + TestAuditJava = flag.Bool("test.audit.Java", false, "Run Java technologies (Maven, Gradle) audit integration tests") + TestAuditCTypes = flag.Bool("test.audit.C", false, "Run C/C++/C# technologies (Nuget/DotNet, Conan) audit integration tests") + TestAuditGo = flag.Bool("test.audit.Go", false, "Run Go technologies (GoLang) audit integration tests") + TestAuditPython = flag.Bool("test.audit.Python", false, "Run Python technologies (Pip, PipEnv, Poetry) audit integration tests") + + JfrogUrl = flag.String("jfrog.url", getTestUrlDefaultValue(), "JFrog platform url") + JfrogUser = flag.String("jfrog.user", getTestUserDefaultValue(), "JFrog platform username") + JfrogPassword = flag.String("jfrog.password", getTestPasswordDefaultValue(), "JFrog platform password") + JfrogAccessToken = flag.String("jfrog.adminToken", os.Getenv(TestJfrogTokenEnvVar), "JFrog platform admin token") - JfrogUrl = flag.String("jfrog.url", "http://localhost:8083/", "JFrog platform url") - JfrogUser = flag.String("jfrog.user", "admin", "JFrog platform username") - JfrogPassword = flag.String("jfrog.password", "password", "JFrog platform password") JfrogSshKeyPath = flag.String("jfrog.sshKeyPath", "", "Ssh key file path") JfrogSshPassphrase = flag.String("jfrog.sshPassphrase", "", "Ssh key passphrase") - JfrogAccessToken = flag.String("jfrog.adminToken", "", "JFrog platform admin token") - - ContainerRegistry = flag.String("test.containerRegistry", "localhost:8083", "Container registry") + ContainerRegistry = flag.String("test.containerRegistry", "localhost:8084", "Container registry") + ciRunId = flag.String("ci.runId", "", "A unique identifier used as a suffix to create repositories and builds in the tests") +} - HideUnitTestLog = flag.Bool("test.hideUnitTestLog", false, "Hide unit tests logs and print it in a file") - SkipUnitTests = flag.Bool("test.skipUnitTests", false, "Skip unit tests") +func InitTestFlags() { + flag.Parse() + // If no test types flags were set, run all types + shouldRunAllTests := !isAtLeastOneFlagSet(TestUnit, TestArtifactory, TestXray, TestXsc, TestAuditGeneral, TestAuditJas, TestAuditJavaScript, TestAuditJava, TestAuditCTypes, TestAuditGo, TestAuditPython, TestScan, TestDockerScan, TestCuration, TestEnrich, TestGit) + if shouldRunAllTests { + log.Info("Running all tests. To run only specific tests, please specify the desired test flags.") + *TestUnit = true + *TestArtifactory = true + *TestXray = true + *TestXsc = true + *TestAuditGeneral = true + *TestAuditJas = true + *TestAuditJavaScript = true + *TestAuditJava = true + *TestAuditCTypes = true + *TestAuditGo = true + *TestAuditPython = true + *TestScan = true + *TestDockerScan = true + *TestCuration = true + *TestEnrich = true + *TestGit = true + } +} - ciRunId = flag.String("ci.runId", "", "A unique identifier used as a suffix to create repositories and builds in the tests") +func isAtLeastOneFlagSet(flagPointers ...*bool) bool { + for _, flagPointer := range flagPointers { + if *flagPointer { + return true + } + } + return false } diff --git a/tests/consts.go b/tests/consts.go index a71e3c7c..4cfd9103 100644 --- a/tests/consts.go +++ b/tests/consts.go @@ -1,6 +1,8 @@ package tests import ( + "os" + "path/filepath" "strconv" "strings" "time" @@ -11,6 +13,11 @@ const ( GoCacheEnvVar = "GOMODCACHE" PipCacheEnvVar = "PIP_CACHE_DIR" + TestJfrogUrlEnvVar = "JFROG_SECURITY_CLI_TESTS_JFROG_URL" + TestJfrogTokenEnvVar = "JFROG_SECURITY_CLI_TESTS_JFROG_ACCESS_TOKEN" + TestJfrogUserEnvVar = "JFROG_SECURITY_CLI_TESTS_JFROG_USER" + TestJfrogPasswordEnvVar = "JFROG_SECURITY_CLI_TESTS_JFROG_PASSWORD" + MavenCacheRedirectionVal = "-Dmaven.repo.local=" ) @@ -90,11 +97,24 @@ var reposConfigMap = map[*string]string{ &PypiRemoteRepo: PypiRemoteRepositoryConfig, } +func GetTestResourcesPath() string { + dir, _ := os.Getwd() + return getTestResourcesPath(dir) +} + +func GetTestResourcesPathFromPath(basePaths ...string) string { + return getTestResourcesPath(filepath.Join(basePaths...)) +} + +func getTestResourcesPath(basePath string) string { + return filepath.Join(basePath, "tests", "testdata") +} + // Return local and remote repositories for the test suites, respectfully func GetNonVirtualRepositories() map[*string]string { nonVirtualReposMap := map[*bool][]*string{ - TestDockerScan: {&DockerLocalRepo, &DockerRemoteRepo}, - TestSecurity: {&NpmRemoteRepo, &NugetRemoteRepo, &YarnRemoteRepo, &GradleRemoteRepo, &MvnRemoteRepo, &MvnRemoteSnapshotsRepo, &GoRepo, &GoRemoteRepo, &PypiRemoteRepo}, + TestDockerScan: {&DockerLocalRepo, &DockerRemoteRepo}, + TestArtifactory: {&NpmRemoteRepo, &NugetRemoteRepo, &YarnRemoteRepo, &GradleRemoteRepo, &MvnRemoteRepo, &MvnRemoteSnapshotsRepo, &GoRepo, &GoRemoteRepo, &PypiRemoteRepo}, } return getNeededRepositories(nonVirtualReposMap) } @@ -102,8 +122,8 @@ func GetNonVirtualRepositories() map[*string]string { // Return virtual repositories for the test suites, respectfully func GetVirtualRepositories() map[*string]string { virtualReposMap := map[*bool][]*string{ - TestDockerScan: {&DockerVirtualRepo}, - TestSecurity: {&GoVirtualRepo, &MvnVirtualRepo}, + TestDockerScan: {&DockerVirtualRepo}, + TestArtifactory: {&GoVirtualRepo, &MvnVirtualRepo}, } return getNeededRepositories(virtualReposMap) } diff --git a/tests/testdata/projects/package-managers/go/missing-context/go.mod b/tests/testdata/projects/package-managers/go/missing-context/go.mod deleted file mode 100644 index c7378018..00000000 --- a/tests/testdata/projects/package-managers/go/missing-context/go.mod +++ /dev/null @@ -1,7 +0,0 @@ -module missing_context - -go 1.22 - -require github.com/hashicorp/consul v1.9.1 - - diff --git a/tests/testdata/projects/package-managers/go/missing-context/go.sum b/tests/testdata/projects/package-managers/go/missing-context/go.sum deleted file mode 100644 index f6029ea1..00000000 --- a/tests/testdata/projects/package-managers/go/missing-context/go.sum +++ /dev/null @@ -1 +0,0 @@ -github.com/hashicorp/consul v1.9.1/go.mod h1:RQlaP4r7KdNLaPDuihkvghhdvZVOuVlUhlz7HtvC1UI= \ No newline at end of file diff --git a/tests/testdata/projects/package-managers/maven/missing-context/pom.xml b/tests/testdata/projects/package-managers/maven/missing-context/pom.xml new file mode 100644 index 00000000..b7dbff6c --- /dev/null +++ b/tests/testdata/projects/package-managers/maven/missing-context/pom.xml @@ -0,0 +1,46 @@ + + 4.0.0 + + org.jfrog + cli-test + 1.0 + jar + + cli-test + http://maven.apache.org + + + UTF-8 + 1.8 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + ${java.version} + ${java.version} + + + + + + + + junit + junit + 4.11 + test + + + org.apache.logging.log4j + log4j-core + 2.15.0 + + + \ No newline at end of file diff --git a/tests/utils/test_config.go b/tests/utils/integration/test_integrationutils.go similarity index 66% rename from tests/utils/test_config.go rename to tests/utils/integration/test_integrationutils.go index a9addb12..bea1acc1 100644 --- a/tests/utils/test_config.go +++ b/tests/utils/integration/test_integrationutils.go @@ -1,4 +1,4 @@ -package utils +package integration import ( "errors" @@ -10,7 +10,10 @@ import ( "github.com/stretchr/testify/assert" + "github.com/jfrog/jfrog-cli-security/cli" configTests "github.com/jfrog/jfrog-cli-security/tests" + testUtils "github.com/jfrog/jfrog-cli-security/tests/utils" + "github.com/jfrog/jfrog-cli-security/utils/xsc" "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/repository" artifactoryUtils "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" @@ -31,6 +34,136 @@ import ( clientTests "github.com/jfrog/jfrog-client-go/utils/tests" ) +func getSkipTestMsg(testName, testFlag string) string { + return fmt.Sprintf("Skipping %s tests. To run them, add the '%s=true' option or don't supply any options.", testName, testFlag) +} + +func InitUnitTest(t *testing.T) { + if !*configTests.TestUnit { + t.Skip(getSkipTestMsg("Unit", "--test.unit")) + } +} + +func InitArtifactoryTest(t *testing.T) { + if !*configTests.TestArtifactory { + t.Skip(getSkipTestMsg("Artifactory integration", "--test.artifactory")) + } +} + +func InitXrayTest(t *testing.T, minVersion string) { + if !*configTests.TestXray { + t.Skip(getSkipTestMsg("Xray commands", "--test.xray")) + } + testUtils.ValidateXrayVersion(t, minVersion) +} + +func GetTestServerDetails() *config.ServerDetails { + return configTests.RtDetails +} + +func InitXscTest(t *testing.T, validations ...func()) func() { + if !*configTests.TestXsc { + t.Skip(getSkipTestMsg("XSC integration", "--test.xsc")) + } + // validate XSC is enabled at the given server + xscManager, err := xsc.CreateXscServiceManager(configTests.XscDetails) + assert.NoError(t, err) + _, err = xscManager.GetVersion() + if err != nil { + t.Skip("Skipping XSC integration tests. XSC is not enabled at the given server.") + } + for _, validation := range validations { + validation() + } + // Make sure the audit request will work with xsc and not xray + assert.NoError(t, os.Setenv(coreutils.ReportUsage, "true")) + return func() { + assert.NoError(t, os.Setenv(coreutils.ReportUsage, "false")) + } +} + +func InitAuditGeneralTests(t *testing.T, minVersion string) { + if !*configTests.TestAuditGeneral { + t.Skip(getSkipTestMsg("Audit command general integration", "--test.audit")) + } + testUtils.ValidateXrayVersion(t, minVersion) +} + +func InitAuditJasTest(t *testing.T, minVersion string) { + if !*configTests.TestAuditJas { + t.Skip(getSkipTestMsg("Audit command JFrog Artifactory Security integration", "--test.audit.Jas")) + } + testUtils.ValidateXrayVersion(t, minVersion) +} + +func InitAuditJavaScriptTest(t *testing.T, minVersion string) { + if !*configTests.TestAuditJavaScript { + t.Skip(getSkipTestMsg("Audit command JavaScript technologies (Npm, Pnpm, Yarn) integration", "--test.audit.JavaScript")) + } + testUtils.ValidateXrayVersion(t, minVersion) +} + +func InitAuditJavaTest(t *testing.T, minVersion string) { + if !*configTests.TestAuditJava { + t.Skip(getSkipTestMsg("Audit command Java technologies (Maven, Gradle) integration", "--test.audit.Java")) + } + testUtils.ValidateXrayVersion(t, minVersion) +} + +func InitAuditCTest(t *testing.T, minVersion string) { + if !*configTests.TestAuditCTypes { + t.Skip(getSkipTestMsg("Audit command C/C++/C# technologies (Nuget/DotNet, Conan) integration", "--test.audit.C")) + } + testUtils.ValidateXrayVersion(t, minVersion) +} + +func InitAuditGoTest(t *testing.T, minVersion string) { + if !*configTests.TestAuditGo { + t.Skip(getSkipTestMsg("Audit command Go technologies (GoLang) integration", "--test.audit.Go")) + } + testUtils.ValidateXrayVersion(t, minVersion) +} + +func InitAuditPythonTest(t *testing.T, minVersion string) { + if !*configTests.TestAuditPython { + t.Skip(getSkipTestMsg("Audit command Python technologies (Pip, PipEnv, Poetry) integration", "--test.audit.Python")) + } + testUtils.ValidateXrayVersion(t, minVersion) +} + +func InitScanTest(t *testing.T, minVersion string) { + if !*configTests.TestScan { + t.Skip(getSkipTestMsg("Other scan commands integration", "--test.scan")) + } + testUtils.ValidateXrayVersion(t, minVersion) +} + +func InitNativeDockerTest(t *testing.T) (mockCli *coreTests.JfrogCli, cleanUp func()) { + if !*configTests.TestDockerScan { + t.Skip(getSkipTestMsg("Docker scan command integration (Ubuntu)", "--test.dockerScan")) + } + return InitTestWithMockCommandOrParams(t, false, cli.DockerScanMockCommand) +} + +func InitCurationTest(t *testing.T) { + if !*configTests.TestCuration { + t.Skip(getSkipTestMsg("Curation command integration", "--test.curation")) + } +} + +func InitEnrichTest(t *testing.T, minVersion string) { + if !*configTests.TestEnrich { + t.Skip(getSkipTestMsg("Enrich command integration", "--test.enrich")) + } + testUtils.ValidateXrayVersion(t, minVersion) +} + +func InitGitTest(t *testing.T) { + if !*configTests.TestGit { + t.Skip(getSkipTestMsg("Git commands integration", "--test.git")) + } +} + func CreateJfrogHomeConfig(t *testing.T, encryptPassword bool) { wd, err := os.Getwd() assert.NoError(t, err, "Failed to get current dir") @@ -253,7 +386,7 @@ func DeleteRepos(repos map[*string]string) { func CreateRepos(repos map[*string]string) { for repoName, configFile := range repos { if !isRepoExist(*repoName) { - repoConfig := GetTestResourcesPath() + "artifactory-repo-configs/" + configFile + repoConfig := configTests.GetTestResourcesPath() + "/artifactory-repo-configs/" + configFile repoConfig, err := commonTests.ReplaceTemplateVariables(repoConfig, "", configTests.GetSubstitutionMap()) if err != nil { log.Error(err) @@ -263,3 +396,17 @@ func CreateRepos(repos map[*string]string) { } } } + +func InitTestWithMockCommandOrParams(t *testing.T, xrayUrlCli bool, mockCommands ...func() components.Command) (mockCli *coreTests.JfrogCli, cleanUp func()) { + oldHomeDir := os.Getenv(coreutils.HomeDir) + // Create server config to use with the command. + CreateJfrogHomeConfig(t, true) + // Create mock cli with the mock commands. + commands := []components.Command{} + for _, mockCommand := range mockCommands { + commands = append(commands, mockCommand()) + } + return GetTestCli(components.CreateEmbeddedApp("security", commands), xrayUrlCli), func() { + clientTests.SetEnvAndAssert(t, coreutils.HomeDir, oldHomeDir) + } +} diff --git a/tests/utils/test_utils.go b/tests/utils/test_utils.go index 7eca74c1..6d4f88b1 100644 --- a/tests/utils/test_utils.go +++ b/tests/utils/test_utils.go @@ -17,18 +17,18 @@ import ( "github.com/jfrog/jfrog-cli-security/utils/results" "github.com/owenrumney/go-sarif/v2/sarif" + biutils "github.com/jfrog/build-info-go/utils" clientUtils "github.com/jfrog/jfrog-client-go/utils" xrayUtils "github.com/jfrog/jfrog-client-go/xray/services/utils" "github.com/stretchr/testify/require" "github.com/jfrog/gofrog/version" - "github.com/jfrog/jfrog-cli-core/v2/plugins/components" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + coreTests "github.com/jfrog/jfrog-cli-core/v2/utils/tests" "github.com/jfrog/jfrog-cli-core/v2/utils/xray" configTests "github.com/jfrog/jfrog-cli-security/tests" "github.com/stretchr/testify/assert" - coreTests "github.com/jfrog/jfrog-cli-core/v2/utils/tests" "github.com/jfrog/jfrog-client-go/utils/io/fileutils" "github.com/jfrog/jfrog-client-go/utils/log" clientTests "github.com/jfrog/jfrog-client-go/utils/tests" @@ -48,13 +48,6 @@ func UnmarshalXML(t *testing.T, output string) formats.Bom { return xmlMap } -func InitSecurityTest(t *testing.T, xrayMinVersion string) { - if !*configTests.TestSecurity { - t.Skip("Skipping Security test. To run Security test add the '-test.security=true' option.") - } - ValidateXrayVersion(t, xrayMinVersion) -} - func ValidateXrayVersion(t *testing.T, minVersion string) { xrayVersion, err := getTestsXrayVersion() if err != nil { @@ -78,25 +71,6 @@ func ValidateXscVersion(t *testing.T, minVersion string) { } } -func InitTestWithMockCommandOrParams(t *testing.T, xrayUrlOnly bool, mockCommands ...func() components.Command) (mockCli *coreTests.JfrogCli, cleanUp func()) { - oldHomeDir := os.Getenv(coreutils.HomeDir) - // Create server config to use with the command. - CreateJfrogHomeConfig(t, true) - // Create mock cli with the mock commands. - commands := []components.Command{} - for _, mockCommand := range mockCommands { - commands = append(commands, mockCommand()) - } - return GetTestCli(components.CreateEmbeddedApp("security", commands), xrayUrlOnly), func() { - clientTests.SetEnvAndAssert(t, coreutils.HomeDir, oldHomeDir) - } -} - -func GetTestResourcesPath() string { - dir, _ := os.Getwd() - return filepath.ToSlash(dir + "/tests/testdata/") -} - func CleanTestsHomeEnv() { os.Unsetenv(coreutils.HomeDir) CleanFileSystem() @@ -307,6 +281,13 @@ func getJasConvertedPath(pathToConvert string) string { return filepath.FromSlash(strings.TrimPrefix(pathToConvert, "file://")) } +func ChangeWDWithCallback(t *testing.T, newPath string) func() { + prevDir := ChangeWD(t, newPath) + return func() { + clientTests.ChangeDirAndAssert(t, prevDir) + } +} + func CreateTestWatch(t *testing.T, policyName string, watchName, severity xrayUtils.Severity) (string, func()) { xrayManager, err := xray.CreateXrayServiceManager(configTests.XrDetails) require.NoError(t, err) @@ -343,3 +324,23 @@ func CreateTestWatch(t *testing.T, policyName string, watchName, severity xrayUt assert.NoError(t, xrayManager.DeletePolicy(policyParams.Name)) } } + +func CreateTestProjectInTempDir(t *testing.T, projectPath string) (string, func()) { + tempDirPath, err := fileutils.CreateTempDir() + assert.NoError(t, err, "Couldn't create temp dir") + actualPath := filepath.Join(filepath.Dir(tempDirPath), filepath.Base(projectPath)) + coreTests.RenamePath(tempDirPath, actualPath, t) + assert.NoError(t, biutils.CopyDir(projectPath, actualPath, true, nil)) + return actualPath, func() { + assert.NoError(t, fileutils.RemoveTempDir(actualPath), "Couldn't remove temp dir") + } +} + +func CreateTestProjectEnvAndChdir(t *testing.T, projectPath string) (string, func()) { + tempDirPath, createTempDirCallback := CreateTestProjectInTempDir(t, projectPath) + prevWd := ChangeWD(t, tempDirPath) + return tempDirPath, func() { + clientTests.ChangeDirAndAssert(t, prevWd) + createTempDirCallback() + } +} diff --git a/unit_test.go b/unit_test.go index 2b7bcc39..407cba3b 100644 --- a/unit_test.go +++ b/unit_test.go @@ -4,7 +4,7 @@ import ( "github.com/stretchr/testify/assert" coreTests "github.com/jfrog/jfrog-cli-core/v2/utils/tests" - configTests "github.com/jfrog/jfrog-cli-security/tests" + "github.com/jfrog/jfrog-cli-security/tests/utils/integration" clientTests "github.com/jfrog/jfrog-client-go/utils/tests" clientLog "github.com/jfrog/jfrog-client-go/utils/log" @@ -18,9 +18,7 @@ const ( ) func TestUnitTests(t *testing.T) { - if *configTests.SkipUnitTests { - t.Skip("Skipping unit tests.") - } + integration.InitUnitTest(t) // Create temp jfrog home cleanUpJfrogHome, err := coreTests.SetJfrogHome() if err != nil { @@ -32,5 +30,5 @@ func TestUnitTests(t *testing.T) { packages := clientTests.GetTestPackages("./...") packages = clientTests.ExcludeTestsPackage(packages, CliIntegrationTests) - assert.NoError(t, clientTests.RunTests(packages, *configTests.HideUnitTestLog)) + assert.NoError(t, clientTests.RunTests(packages, false)) } diff --git a/utils/parallel_runner.go b/utils/parallel_runner.go index b4068091..15e9fa62 100644 --- a/utils/parallel_runner.go +++ b/utils/parallel_runner.go @@ -7,26 +7,17 @@ import ( type SecurityParallelRunner struct { Runner parallel.Runner - ErrorsQueue chan error ResultsMu sync.Mutex ScaScansWg sync.WaitGroup // Verify that the sca scan routines are done before running contextual scan JasScannersWg sync.WaitGroup // Verify that all scanners routines are done before cleaning temp dir JasWg sync.WaitGroup // Verify that downloading analyzer manager and running all scanners are done - ErrWg sync.WaitGroup // Verify that all errors are handled before finishing the audit func } func NewSecurityParallelRunner(numOfParallelScans int) SecurityParallelRunner { - return SecurityParallelRunner{ - Runner: parallel.NewRunner(numOfParallelScans, 20000, false), - ErrorsQueue: make(chan error, 100), - } + return SecurityParallelRunner{Runner: parallel.NewRunner(numOfParallelScans, 20000, false)} } func CreateSecurityParallelRunner(numOfParallelScans int) *SecurityParallelRunner { securityParallelRunner := NewSecurityParallelRunner(numOfParallelScans) return &securityParallelRunner } - -func (spr *SecurityParallelRunner) AddErrorToChan(err error) { - spr.ErrorsQueue <- err -} diff --git a/utils/results/conversion/convertor.go b/utils/results/conversion/convertor.go index e95ceed7..f2c044db 100644 --- a/utils/results/conversion/convertor.go +++ b/utils/results/conversion/convertor.go @@ -46,7 +46,7 @@ func NewCommandResultsConvertor(params ResultConvertParams) *CommandResultsConve // Parse a stream of results and convert them to the desired format T type ResultsStreamFormatParser[T interface{}] interface { // Reset the convertor to start converting a new command results - Reset(cmdType utils.CommandType, multiScanId, xrayVersion string, entitledForJas, multipleTargets bool) error + Reset(cmdType utils.CommandType, multiScanId, xrayVersion string, entitledForJas, multipleTargets bool, generalError error) error // Will be called for each scan target (indicating the current is done parsing and starting to parse a new scan) ParseNewTargetResults(target results.ScanTarget, errors ...error) error // Parse SCA content to the current scan target @@ -87,7 +87,7 @@ func parseCommandResults[T interface{}](params ResultConvertParams, parser Resul if params.IsMultipleRoots != nil { multipleTargets = *params.IsMultipleRoots } - if err = parser.Reset(cmdResults.CmdType, cmdResults.MultiScanId, cmdResults.XrayVersion, jasEntitled, multipleTargets); err != nil { + if err = parser.Reset(cmdResults.CmdType, cmdResults.MultiScanId, cmdResults.XrayVersion, jasEntitled, multipleTargets, cmdResults.GeneralError); err != nil { return } for _, targetScansResults := range cmdResults.Targets { diff --git a/utils/results/conversion/sarifparser/sarifparser.go b/utils/results/conversion/sarifparser/sarifparser.go index dd51fbb6..9e0950f9 100644 --- a/utils/results/conversion/sarifparser/sarifparser.go +++ b/utils/results/conversion/sarifparser/sarifparser.go @@ -76,7 +76,7 @@ func (sc *CmdResultsSarifConverter) Get() (*sarif.Report, error) { return sc.current, nil } -func (sc *CmdResultsSarifConverter) Reset(cmdType utils.CommandType, _, xrayVersion string, entitledForJas, _ bool) (err error) { +func (sc *CmdResultsSarifConverter) Reset(cmdType utils.CommandType, _, xrayVersion string, entitledForJas, _ bool, _ error) (err error) { sc.current, err = sarifutils.NewReport() if err != nil { return @@ -298,7 +298,7 @@ func parseScaToSarifFormat(cmdType utils.CommandType, xrayId, summary, markdownD issueId := results.GetIssueIdentifier(cves, xrayId, "_") cveImpactedComponentRuleId := results.GetScaIssueId(impactedPackagesName, impactedPackagesVersion, issueId) level := severityutils.SeverityToSarifSeverityLevel(severity) - // Add rule fpr the cve if not exists + // Add rule for the cve if not exists rule = getScaIssueSarifRule( cveImpactedComponentRuleId, generateTitleFunc(impactedPackagesName, impactedPackagesVersion, issueId), diff --git a/utils/results/conversion/simplejsonparser/simplejsonparser.go b/utils/results/conversion/simplejsonparser/simplejsonparser.go index 12e304b6..e390f924 100644 --- a/utils/results/conversion/simplejsonparser/simplejsonparser.go +++ b/utils/results/conversion/simplejsonparser/simplejsonparser.go @@ -43,10 +43,13 @@ func (sjc *CmdResultsSimpleJsonConverter) Get() (formats.SimpleJsonResults, erro return *sjc.current, nil } -func (sjc *CmdResultsSimpleJsonConverter) Reset(_ utils.CommandType, multiScanId, _ string, entitledForJas, multipleTargets bool) (err error) { +func (sjc *CmdResultsSimpleJsonConverter) Reset(_ utils.CommandType, multiScanId, _ string, entitledForJas, multipleTargets bool, generalError error) (err error) { sjc.current = &formats.SimpleJsonResults{MultiScanId: multiScanId} sjc.entitledForJas = entitledForJas sjc.multipleRoots = multipleTargets + if generalError != nil { + sjc.current.Errors = append(sjc.current.Errors, formats.SimpleJsonError{ErrorMessage: generalError.Error()}) + } return } diff --git a/utils/results/conversion/summaryparser/summaryparser.go b/utils/results/conversion/summaryparser/summaryparser.go index e07c99ba..58f3f1e0 100644 --- a/utils/results/conversion/summaryparser/summaryparser.go +++ b/utils/results/conversion/summaryparser/summaryparser.go @@ -35,7 +35,7 @@ func (sc *CmdResultsSummaryConverter) Get() (formats.ResultsSummary, error) { return *sc.current, nil } -func (sc *CmdResultsSummaryConverter) Reset(_ utils.CommandType, _, _ string, entitledForJas, _ bool) (err error) { +func (sc *CmdResultsSummaryConverter) Reset(_ utils.CommandType, _, _ string, entitledForJas, _ bool, _ error) (err error) { sc.current = &formats.ResultsSummary{} sc.entitledForJas = entitledForJas return diff --git a/utils/results/conversion/tableparser/tableparser.go b/utils/results/conversion/tableparser/tableparser.go index 5383946f..09345f8b 100644 --- a/utils/results/conversion/tableparser/tableparser.go +++ b/utils/results/conversion/tableparser/tableparser.go @@ -30,6 +30,7 @@ func (tc *CmdResultsTableConverter) Get() (formats.ResultsTables, error) { SecurityVulnerabilitiesTable: formats.ConvertToVulnerabilityTableRow(simpleJsonFormat.Vulnerabilities), SecurityViolationsTable: formats.ConvertToVulnerabilityTableRow(simpleJsonFormat.SecurityViolations), LicenseViolationsTable: formats.ConvertToLicenseViolationTableRow(simpleJsonFormat.LicensesViolations), + LicensesTable: formats.ConvertToLicenseTableRow(simpleJsonFormat.Licenses), OperationalRiskViolationsTable: formats.ConvertToOperationalRiskViolationTableRow(simpleJsonFormat.OperationalRiskViolations), SecretsTable: formats.ConvertToSecretsTableRow(simpleJsonFormat.Secrets), IacTable: formats.ConvertToIacOrSastTableRow(simpleJsonFormat.Iacs), @@ -37,8 +38,8 @@ func (tc *CmdResultsTableConverter) Get() (formats.ResultsTables, error) { }, nil } -func (tc *CmdResultsTableConverter) Reset(cmdType utils.CommandType, multiScanId, xrayVersion string, entitledForJas, multipleTargets bool) (err error) { - return tc.simpleJsonConvertor.Reset(cmdType, multiScanId, xrayVersion, entitledForJas, multipleTargets) +func (tc *CmdResultsTableConverter) Reset(cmdType utils.CommandType, multiScanId, xrayVersion string, entitledForJas, multipleTargets bool, generalError error) (err error) { + return tc.simpleJsonConvertor.Reset(cmdType, multiScanId, xrayVersion, entitledForJas, multipleTargets, generalError) } func (tc *CmdResultsTableConverter) ParseNewTargetResults(target results.ScanTarget, errors ...error) (err error) { diff --git a/utils/results/results.go b/utils/results/results.go index cc495fa0..2f454c87 100644 --- a/utils/results/results.go +++ b/utils/results/results.go @@ -10,6 +10,7 @@ import ( "github.com/jfrog/jfrog-cli-security/utils" "github.com/jfrog/jfrog-cli-security/utils/jasutils" "github.com/jfrog/jfrog-cli-security/utils/techutils" + "github.com/jfrog/jfrog-client-go/utils/log" "github.com/jfrog/jfrog-client-go/xray/services" "github.com/owenrumney/go-sarif/v2/sarif" ) @@ -26,8 +27,9 @@ type SecurityCommandResults struct { // Results for each target in the command Targets []*TargetResults `json:"targets"` targetsMutex sync.Mutex `json:"-"` - // Error that occurred during the command execution - Error error `json:"error,omitempty"` + // GeneralError that occurred during the command execution + GeneralError error `json:"general_error,omitempty"` + errorsMutex sync.Mutex `json:"-"` } type TargetResults struct { @@ -79,8 +81,27 @@ func (st ScanTarget) String() (str string) { return } -func NewCommandResults(cmdType utils.CommandType, xrayVersion string, entitledForJas, secretValidation bool) *SecurityCommandResults { - return &SecurityCommandResults{CmdType: cmdType, XrayVersion: xrayVersion, EntitledForJas: entitledForJas, SecretValidation: secretValidation, targetsMutex: sync.Mutex{}} +// func NewCommandResults(cmdType utils.CommandType, xrayVersion string, entitledForJas, secretValidation bool) *SecurityCommandResults { +// return &SecurityCommandResults{CmdType: cmdType, XrayVersion: xrayVersion, EntitledForJas: entitledForJas, SecretValidation: secretValidation, targetsMutex: sync.Mutex{}} +// } + +func NewCommandResults(cmdType utils.CommandType) *SecurityCommandResults { + return &SecurityCommandResults{CmdType: cmdType, targetsMutex: sync.Mutex{}, errorsMutex: sync.Mutex{}} +} + +func (r *SecurityCommandResults) SetXrayVersion(xrayVersion string) *SecurityCommandResults { + r.XrayVersion = xrayVersion + return r +} + +func (r *SecurityCommandResults) SetEntitledForJas(entitledForJas bool) *SecurityCommandResults { + r.EntitledForJas = entitledForJas + return r +} + +func (r *SecurityCommandResults) SetSecretValidation(secretValidation bool) *SecurityCommandResults { + r.SecretValidation = secretValidation + return r } func (r *SecurityCommandResults) SetMultiScanId(multiScanId string) *SecurityCommandResults { @@ -90,6 +111,11 @@ func (r *SecurityCommandResults) SetMultiScanId(multiScanId string) *SecurityCom // --- Aggregated results for all targets --- +func (r *SecurityCommandResults) AddGeneralError(err error) *SecurityCommandResults { + r.GeneralError = errors.Join(r.GeneralError, err) + return r +} + func (r *SecurityCommandResults) GetTargetsPaths() (paths []string) { for _, scan := range r.Targets { paths = append(paths, scan.Target) @@ -115,7 +141,7 @@ func (r *SecurityCommandResults) GetJasScansResults(scanType jasutils.JasScanTyp } func (r *SecurityCommandResults) GetErrors() (err error) { - err = r.Error + err = r.GeneralError for _, target := range r.Targets { if targetErr := target.GetErrors(); targetErr != nil { err = errors.Join(err, fmt.Errorf("target '%s' errors:\n%s", target.String(), targetErr)) @@ -266,10 +292,15 @@ func (sr *TargetResults) HasFindings() bool { return false } -func (sr *TargetResults) AddError(err error) { +func (sr *TargetResults) AddTargetError(err error, allowPartialResults bool) error { + if allowPartialResults { + log.Warn(fmt.Sprintf("Partial results are allowed, the error is skipped in target '%s': %s", sr.String(), err.Error())) + return nil + } sr.errorsMutex.Lock() sr.Errors = append(sr.Errors, err) sr.errorsMutex.Unlock() + return err } func (sr *TargetResults) SetDescriptors(descriptors ...string) *TargetResults { diff --git a/utils/xsc/analyticsmetrics_test.go b/utils/xsc/analyticsmetrics_test.go index eb723a4a..71d5b19a 100644 --- a/utils/xsc/analyticsmetrics_test.go +++ b/utils/xsc/analyticsmetrics_test.go @@ -112,7 +112,7 @@ func TestAnalyticsMetricsService_createAuditResultsFromXscAnalyticsBasicGeneralE func getDummyContentForGeneralEvent(withJas, withErr bool) *results.SecurityCommandResults { vulnerabilities := []services.Vulnerability{{IssueId: "XRAY-ID", Severity: "medium", Cves: []services.Cve{{Id: "CVE-123"}}, Components: map[string]services.Component{"issueId_2_direct_dependency": {}}}} - cmdResults := results.NewCommandResults(utils.SourceCode, "", true, true) + cmdResults := results.NewCommandResults(utils.SourceCode).SetEntitledForJas(true).SetSecretValidation(true) scanResults := cmdResults.NewScanResults(results.ScanTarget{Target: "target"}) scanResults.NewScaScanResults(services.ScanResponse{Vulnerabilities: vulnerabilities}) diff --git a/utils/xsc/errorreport.go b/utils/xsc/errorreport.go index ee14e889..fec947f2 100644 --- a/utils/xsc/errorreport.go +++ b/utils/xsc/errorreport.go @@ -2,6 +2,7 @@ package xsc import ( "fmt" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" clientutils "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/log" @@ -44,7 +45,7 @@ func IsReportLogErrorEventPossible(xscManager *xsc.XscServicesManager) bool { log.Debug("Xsc service is not available. Reporting to JFrog analytics is skipped...") return false } - if err = clientutils.ValidateMinimumVersion(clientutils.Xsc, xscVersion, minXscVersionForErrorReport); err != nil { + if err = clientutils.ValidateMinimumVersion(clientutils.Xsc, xscVersion, MinXscVersionForErrorReport); err != nil { log.Debug(err.Error()) return false } diff --git a/utils/xsc/errorreport_test.go b/utils/xsc/errorreport_test.go index 2fc77db8..42c7f6af 100644 --- a/utils/xsc/errorreport_test.go +++ b/utils/xsc/errorreport_test.go @@ -14,7 +14,7 @@ import ( const ( unsupportedXscVersionForErrorLogs = "1.6.0" - supportedXscVersionForErrorLogs = minXscVersionForErrorReport + supportedXscVersionForErrorLogs = MinXscVersionForErrorReport ) func TestReportLogErrorEventPossible(t *testing.T) { diff --git a/utils/xsc/xscmanager.go b/utils/xsc/xscmanager.go index 985d6788..45dff324 100644 --- a/utils/xsc/xscmanager.go +++ b/utils/xsc/xscmanager.go @@ -2,6 +2,7 @@ package xsc import ( "fmt" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" clientconfig "github.com/jfrog/jfrog-client-go/config" @@ -9,7 +10,7 @@ import ( "github.com/jfrog/jfrog-client-go/xsc" ) -const minXscVersionForErrorReport = "1.7.7" +const MinXscVersionForErrorReport = "1.7.7" func CreateXscServiceManager(serviceDetails *config.ServerDetails) (*xsc.XscServicesManager, error) { certsPath, err := coreutils.GetJfrogCertsDir() diff --git a/xray_test.go b/xray_test.go index 695162dc..8491c70a 100644 --- a/xray_test.go +++ b/xray_test.go @@ -7,13 +7,15 @@ import ( securityDocs "github.com/jfrog/jfrog-cli-security/cli/docs" securityTests "github.com/jfrog/jfrog-cli-security/tests" securityTestUtils "github.com/jfrog/jfrog-cli-security/tests/utils" + "github.com/jfrog/jfrog-cli-security/tests/utils/integration" + securityIntegrationTestUtils "github.com/jfrog/jfrog-cli-security/tests/utils/integration" "github.com/stretchr/testify/assert" ) func TestXrayCurl(t *testing.T) { - securityTestUtils.InitSecurityTest(t, "") + integration.InitXrayTest(t, "") // Configure a new server named "default". - securityTestUtils.CreateJfrogHomeConfig(t, true) + securityIntegrationTestUtils.CreateJfrogHomeConfig(t, true) defer securityTestUtils.CleanTestsHomeEnv() // Check curl command with the default configured server. err := securityTests.PlatformCli.WithoutCredentials().Exec("xr", "curl", "-XGET", "/api/v1/system/version") @@ -27,7 +29,7 @@ func TestXrayCurl(t *testing.T) { } func TestXrayOfflineDBSyncV3(t *testing.T) { - securityTestUtils.InitSecurityTest(t, "") + integration.InitXrayTest(t, "") // Validate license-id err := securityTests.PlatformCli.WithoutCredentials().Exec("xr", "ou") assert.EqualError(t, err, "Mandatory flag 'license-id' is missing") diff --git a/xsc_test.go b/xsc_test.go index 28787968..7a593c4a 100644 --- a/xsc_test.go +++ b/xsc_test.go @@ -3,60 +3,34 @@ package main import ( "encoding/json" "errors" - "os" "testing" "github.com/stretchr/testify/assert" "github.com/jfrog/jfrog-cli-core/v2/common/format" - "github.com/jfrog/jfrog-cli-core/v2/utils/config" - "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" "github.com/jfrog/jfrog-cli-security/utils/formats" "github.com/jfrog/jfrog-cli-security/utils/validations" - "github.com/jfrog/jfrog-cli-security/utils/xray/scangraph" "github.com/jfrog/jfrog-cli-security/utils/xsc" "github.com/jfrog/jfrog-cli-security/tests" securityTestUtils "github.com/jfrog/jfrog-cli-security/tests/utils" - clientTests "github.com/jfrog/jfrog-client-go/utils/tests" + "github.com/jfrog/jfrog-cli-security/tests/utils/integration" xscservices "github.com/jfrog/jfrog-client-go/xsc/services" ) func TestReportError(t *testing.T) { - serverDetails := &config.ServerDetails{ - Url: *tests.JfrogUrl, - ArtifactoryUrl: *tests.JfrogUrl + tests.ArtifactoryEndpoint, - XrayUrl: *tests.JfrogUrl + tests.XrayEndpoint, - AccessToken: *tests.JfrogAccessToken, - ServerId: tests.ServerId, - } - - // Prior to initiating the test, we verify whether Xsc is enabled for the customer. If not, the test is skipped. - xscManager, err := xsc.CreateXscServiceManager(serverDetails) - assert.NoError(t, err) - - if !xsc.IsReportLogErrorEventPossible(xscManager) { - t.Skip("Skipping test since Xsc server is not enabled or below minimal required version") - } - + cleanUp := integration.InitXscTest(t, func() { securityTestUtils.ValidateXscVersion(t, xsc.MinXscVersionForErrorReport) }) + defer cleanUp() errorToReport := errors.New("THIS IS NOT A REAL ERROR! This Error is posted as part of TestReportError test") - assert.NoError(t, xsc.ReportError(serverDetails, errorToReport, "cli")) -} - -func initXscTest(t *testing.T) func() { - // Make sure the audit request will work with xsc and not xray - assert.NoError(t, os.Setenv(coreutils.ReportUsage, "true")) - return func() { - assert.NoError(t, os.Setenv(coreutils.ReportUsage, "false")) - } + assert.NoError(t, xsc.ReportError(tests.XscDetails, errorToReport, "cli")) } // In the npm tests we use a watch flag, so we would get only violations func TestXscAuditNpmJsonWithWatch(t *testing.T) { - restoreFunc := initXscTest(t) - defer restoreFunc() + cleanUp := integration.InitXscTest(t) + defer cleanUp() output := testAuditNpm(t, string(format.Json), false) validations.VerifyJsonResults(t, output, validations.ValidationParams{ SecurityViolations: 1, @@ -65,8 +39,8 @@ func TestXscAuditNpmJsonWithWatch(t *testing.T) { } func TestXscAuditNpmSimpleJsonWithWatch(t *testing.T) { - restoreFunc := initXscTest(t) - defer restoreFunc() + cleanUp := integration.InitXscTest(t) + defer cleanUp() output := testAuditNpm(t, string(format.SimpleJson), true) validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{ SecurityViolations: 1, @@ -76,8 +50,8 @@ func TestXscAuditNpmSimpleJsonWithWatch(t *testing.T) { } func TestXscAuditMavenJson(t *testing.T) { - restoreFunc := initXscTest(t) - defer restoreFunc() + cleanUp := integration.InitXscTest(t) + defer cleanUp() output := testXscAuditMaven(t, string(format.Json)) validations.VerifyJsonResults(t, output, validations.ValidationParams{ Vulnerabilities: 1, @@ -86,8 +60,8 @@ func TestXscAuditMavenJson(t *testing.T) { } func TestXscAuditMavenSimpleJson(t *testing.T) { - restoreFunc := initXscTest(t) - defer restoreFunc() + cleanUp := integration.InitXscTest(t) + defer cleanUp() output := testXscAuditMaven(t, string(format.SimpleJson)) validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{ Vulnerabilities: 1, @@ -96,10 +70,8 @@ func TestXscAuditMavenSimpleJson(t *testing.T) { } func TestXscAnalyticsForAudit(t *testing.T) { - securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion) - securityTestUtils.ValidateXscVersion(t, xscservices.AnalyticsMetricsMinXscVersion) - reportUsageCallBack := clientTests.SetEnvWithCallbackAndAssert(t, coreutils.ReportUsage, "true") - defer reportUsageCallBack() + cleanUp := integration.InitXscTest(t) + defer cleanUp() // Scan npm project and verify that analytics general event were sent to XSC. output := testAuditNpm(t, string(format.SimpleJson), false) validateAnalyticsBasicEvent(t, output) @@ -129,9 +101,9 @@ func validateAnalyticsBasicEvent(t *testing.T, output string) { } func TestAdvancedSecurityDockerScanWithXsc(t *testing.T) { - testCli, cleanup := initNativeDockerWithXrayTest(t) - restoreFunc := initXscTest(t) - defer restoreFunc() - defer cleanup() + cleanUpXsc := integration.InitXscTest(t) + defer cleanUpXsc() + testCli, cleanupDocker := integration.InitNativeDockerTest(t) + defer cleanupDocker() runAdvancedSecurityDockerScan(t, testCli, "jfrog/demo-security:latest") }