diff --git a/.github/workflows/analysis.yml b/.github/workflows/analysis.yml index 7e85f9ef8..8de1fc203 100644 --- a/.github/workflows/analysis.yml +++ b/.github/workflows/analysis.yml @@ -13,31 +13,19 @@ jobs: - name: Checkout Source uses: actions/checkout@v4 - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: 1.22.x - cache: false - - - name: Static Code Analysis - uses: golangci/golangci-lint-action@v4 - 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: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main + - name: Run golangci lint + uses: jfrog/.github/actions/golangci-lint@main Go-Sec: runs-on: ubuntu-latest steps: - name: Checkout Source uses: actions/checkout@v4 - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: 1.22.x - cache: false + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main - - name: Run Gosec Security Scanner - uses: securego/gosec@master - with: - args: -exclude G204,G301,G302,G304,G306 -tests -exclude-dir \.*test\.* ./... + - name: Run Go-Sec scanner + uses: jfrog/.github/actions/gosec-scanner@main diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index cb8cbee95..3ca32e886 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -9,26 +9,10 @@ jobs: CLAssistant: runs-on: ubuntu-latest steps: - - uses: actions-ecosystem/action-regex-match@v2 - id: sign-or-recheck + - name: Run CLA Check + uses: jfrog/.github/actions/cla@main with: - text: ${{ github.event.comment.body }} - regex: '\s*(I have read the CLA Document and I hereby sign the CLA)|(recheck)\s*' - - - name: "CLA Assistant" - if: ${{ steps.sign-or-recheck.outputs.match != '' || github.event_name == 'pull_request_target' }} - # Alpha Release - uses: cla-assistant/github-action@v2.3.0 - env: - # Generated and maintained by GitHub + event_comment_body: ${{ github.event.comment.body }} + event_name: ${{ github.event_name }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # JFrog organization secret - PERSONAL_ACCESS_TOKEN: ${{ secrets.CLA_SIGN_TOKEN }} - with: - path-to-signatures: 'signed_clas.json' - path-to-document: 'https://jfrog.com/cla/' - remote-organization-name: 'jfrog' - remote-repository-name: 'jfrog-signed-clas' - # branch should not be protected - branch: 'master' - allowlist: bot* + CLA_SIGN_TOKEN: ${{ secrets.CLA_SIGN_TOKEN }} diff --git a/.github/workflows/frogbot-scan-pull-request.yml b/.github/workflows/frogbot-scan-pull-request.yml index 998c8c91f..647978728 100644 --- a/.github/workflows/frogbot-scan-pull-request.yml +++ b/.github/workflows/frogbot-scan-pull-request.yml @@ -12,6 +12,9 @@ jobs: # "frogbot" GitHub environment can approve the pull request to be scanned. environment: frogbot steps: + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main + - uses: jfrog/frogbot@v2 env: JFROG_CLI_LOG_LEVEL: "DEBUG" diff --git a/.github/workflows/frogbot-scan-repository.yml b/.github/workflows/frogbot-scan-repository.yml index 01b568f67..ba64a54c4 100644 --- a/.github/workflows/frogbot-scan-repository.yml +++ b/.github/workflows/frogbot-scan-repository.yml @@ -10,12 +10,16 @@ permissions: security-events: write jobs: scan-repository: + name: Scan Repository (${{ matrix.branch }} branch) runs-on: ubuntu-latest strategy: matrix: # The repository scanning will be triggered periodically on the following branches. branch: [ "dev" ] steps: + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main + - uses: jfrog/frogbot@v2 env: JFROG_CLI_LOG_LEVEL: "DEBUG" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index deceff891..16db06872 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -29,18 +29,8 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha }} - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: 1.22.2 - cache: false - - - name: Go Cache - uses: actions/cache@v4 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ runner.os }}-go- + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main - name: Lint run: go vet -v ./... @@ -55,32 +45,18 @@ jobs: os: [ ubuntu, windows, macos ] runs-on: ${{ matrix.os }}-latest steps: - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: 1.22.2 - cache: false - - name: Checkout code uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} - - name: Go Cache - uses: actions/cache@v4 + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main + + - name: Install local Artifactory + uses: jfrog/.github/actions/install-local-artifactory@main with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ runner.os }}-go- - - - name: Setup Artifactory - env: - setup-url: github.com/jfrog/jfrog-testing-infra/local-rt-setup@latest - RTLIC: ${{secrets.RTLIC}} - GOPROXY: direct - run: | - go install ${{ env.setup-url }} || go install ${{ env.setup-url }} - ~/go/bin/local-rt-setup + RTLIC: ${{ secrets.RTLIC }} - name: artifactory tests run: go test -v github.com/jfrog/jfrog-client-go/tests --timeout 0 --test.${{ matrix.suite }} --ci.runId=${{ runner.os }}-${{ matrix.suite }} @@ -100,18 +76,8 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha }} - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: 1.22.2 - cache: false - - - name: Go Cache - uses: actions/cache@v4 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ runner.os }}-go- + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main - name: ${{ matrix.suite }} tests run: go test -v github.com/jfrog/jfrog-client-go/tests --timeout 0 --test.${{ matrix.suite }} --rt.url=${{ secrets.PLATFORM_URL }}/artifactory --ds.url=${{ secrets.PLATFORM_URL }}/distribution --xr.url=${{ secrets.PLATFORM_URL }}/xray --xsc.url=${{ secrets.PLATFORM_URL }}/xsc --access.url=${{ secrets.PLATFORM_URL }}/access --rt.user=${{ secrets.PLATFORM_USER }} --rt.password=${{ secrets.PLATFORM_PASSWORD }} --access.token=${{ secrets.PLATFORM_ADMIN_TOKEN }} --ci.runId=${{ runner.os }}-${{ matrix.suite }} @@ -126,18 +92,8 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha }} - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: 1.22.2 - cache: false - - - name: Go Cache - uses: actions/cache@v4 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ runner.os }}-go- + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main - name: pipelines tests run: go test -v github.com/jfrog/jfrog-client-go/tests --timeout 0 --test.pipelines --rt.url=${{ secrets.PLATFORM_URL }}/artifactory --pipe.url=${{ secrets.PLATFORM_URL }}/pipelines --rt.user=${{ secrets.PLATFORM_USER }} --rt.password=${{ secrets.PLATFORM_PASSWORD }} --pipe.accessToken=${{ secrets.PLATFORM_ADMIN_TOKEN }} --pipe.vcsToken=${{ secrets.CLI_PIPE_VCS_TOKEN }} --pipe.vcsRepo=${{ secrets.CLI_PIPE_VCS_REPO }} --pipe.vcsBranch=${{ secrets.CLI_PIPE_VCS_BRANCH }} --ci.runId=${{ runner.os }}_pipe @@ -147,32 +103,18 @@ jobs: name: repositories ubuntu-latest runs-on: ubuntu-latest steps: - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: 1.22.2 - cache: false - - name: Checkout code uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} - - name: Go Cache - uses: actions/cache@v4 + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main + + - name: Install local Artifactory + uses: jfrog/.github/actions/install-local-artifactory@main with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ runner.os }}-go- - - - name: Setup Artifactory - env: - setup-url: github.com/jfrog/jfrog-testing-infra/local-rt-setup@latest - RTLIC: ${{secrets.RTLIC}} - GOPROXY: direct - run: | - go install ${{ env.setup-url }} || go install ${{ env.setup-url }} - ~/go/bin/local-rt-setup + RTLIC: ${{ secrets.RTLIC }} - name: Repositories tests run: go test -v github.com/jfrog/jfrog-client-go/tests --timeout 0 --test.repositories diff --git a/README.md b/README.md index 63910b382..6167f726b 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,9 @@ - [Get an Xray Policy](#get-an-xray-policy) - [Update an Xray Policy](#update-an-xray-policy) - [Delete an Xray Policy](#delete-an-xray-policy) + - [Create an Xray Ignore Rule](#create-an-xray-ignore-rule) + - [Get an Xray Ignore Rule](#get-an-xray-ignore-rule) + - [Delete an Xray Ignore Rule](#delete-an-xray-ignore-rule) - [Add Builds to Indexing Configuration](#add-builds-to-indexing-configuration) - [Request Graph Scan](#request-graph-scan) - [Retrieve the Graph Scan Results](#retrieve-the-graph-scan-results) @@ -1985,6 +1988,91 @@ err := xrayManager.UpdatePolicy(*policy) err := xrayManager.DeletePolicy("example-policy") ``` +#### Create an Xray Ignore Rule + +```go +params := utils.NewIgnoreRuleParams() +params.Notes := "random-notes-for-ignore-rules" +params.ExpiredAt := time.Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) +params.IgnoreFilters := IgnoreFilters{ + Vulnerabilities: []string{"XRAY-12345", "XRAY-67891"}, + Licenses: []string{"MIT", "BSD"}, + CVEs: []string{"CVE-2021-1234", "CVE-2022-5678"}, + Policies: []string{"policy-name-1", "policy-name-2"}, + Watches: []string{"watch-name-1", "watch-name-2"}, + DockerLayers: []string{"0503825856099e6adb39c8297af09547f69684b7016b7f3680ed801aa310baaa"}, + OperationalRisks: []string{"any"}, + Exposures: []ExposuresFilterName{ + { + FilePath: []string{"/path/to/file1", "/path/to/file2"}, + Scanners: []string{"EXP-12345"}, + Catagories: []ExposuresCatagories{ + { + Secrets: true, + Services: true, + Applications: true, + Iac: true, + }, + }, + }, + }, + ReleaseBundles: []IgnoreFilterNameVersion{ + { + Name: "RB-name", + Version: "0.0.0", + }, + { + Name: "RB-name-2", + Version: "1.2.3", + }, + }, + Builds: []IgnoreFilterNameVersion{ + { + Name: "build-name", + Version: "0.0.0", + }, + { + Name: "build-name-2", + Version: "1.2.3", + }, + }, + Components: []IgnoreFilterNameVersion{ + { + Name: "component-name", + Version: "0.0.0", + }, + { + Name: "component-name-2", + Version: "1.2.3", + }, + }, + Arti: []IgnoreFilterNameVersion{ + { + Name: "artifact-name", + Version: "0.0.0", + }, + { + Name: "artifact-name-2", + Version: "1.2.3", + }, + }, +} + +ignoreRuleIgnoreId, err := xrayManager.CreateIgnoreRule(params) +``` + +#### Get an Xray Ignore Rule + +```go +ignoreRule, err := xrayManager.GetIgnoreRule("ignore-rule-id") +``` + +#### Delete an Xray Ignore Rule + +```go +err := xrayManager.DeleteIgnoreRule("ignore-rule-id") +``` + #### Add Builds to Indexing Configuration ```go diff --git a/artifactory/services/upload.go b/artifactory/services/upload.go index a1360faea..d365c4e3a 100644 --- a/artifactory/services/upload.go +++ b/artifactory/services/upload.go @@ -965,7 +965,9 @@ func (us *UploadService) addFileToZip(artifact *clientutils.Artifact, progressPr return err } defer func() { - err = errors.Join(err, errorutils.CheckError(file.Close())) + if file != nil { + err = errors.Join(err, errorutils.CheckError(file.Close())) + } }() if us.Progress != nil { progressReader := us.Progress.NewProgressReader(info.Size(), progressPrefix, localPath) diff --git a/artifactory/services/utils/multipartupload.go b/artifactory/services/utils/multipartupload.go index b9d7b9880..308aedab4 100644 --- a/artifactory/services/utils/multipartupload.go +++ b/artifactory/services/utils/multipartupload.go @@ -234,7 +234,9 @@ func (mu *MultipartUpload) uploadPart(logMsgPrefix, localPath string, fileSize, return errorutils.CheckError(err) } defer func() { - err = errors.Join(err, errorutils.CheckError(file.Close())) + if file != nil { + err = errors.Join(err, errorutils.CheckError(file.Close())) + } }() if _, err = file.Seek(partId*chunkSize, io.SeekStart); err != nil { return errorutils.CheckError(err) diff --git a/go.mod b/go.mod index 0674e0bc6..c04dcd74d 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/golang-jwt/jwt/v4 v4.5.0 github.com/gookit/color v1.5.4 github.com/jfrog/archiver/v3 v3.6.1 - github.com/jfrog/build-info-go v1.9.29 + github.com/jfrog/build-info-go v1.9.30 github.com/jfrog/gofrog v1.7.4 github.com/minio/sha256-simd v1.0.1 github.com/stretchr/testify v1.9.0 @@ -22,7 +22,7 @@ require ( require ( dario.cat/mergo v1.0.0 // indirect - github.com/CycloneDX/cyclonedx-go v0.8.0 // indirect + github.com/CycloneDX/cyclonedx-go v0.9.0 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/andybalholm/brotli v1.1.0 // indirect github.com/cloudflare/circl v1.3.7 // indirect @@ -58,6 +58,6 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) -// replace github.com/jfrog/build-info-go => github.com/eyalbe4/build-info-go v1.8.6-0.20240610015232-844595d5a4f3 +// replace github.com/jfrog/build-info-go => github.com/eyalbe4/build-info-go v1.8.9-0.20240723132035-980d2c84b738 // replace github.com/jfrog/gofrog => github.com/jfrog/gofrog dev diff --git a/go.sum b/go.sum index f55359840..fa0f9d3ca 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -github.com/CycloneDX/cyclonedx-go v0.8.0 h1:FyWVj6x6hoJrui5uRQdYZcSievw3Z32Z88uYzG/0D6M= -github.com/CycloneDX/cyclonedx-go v0.8.0/go.mod h1:K2bA+324+Og0X84fA8HhN2X066K7Bxz4rpMQ4ZhjtSk= +github.com/CycloneDX/cyclonedx-go v0.9.0 h1:inaif7qD8bivyxp7XLgxUYtOXWtDez7+j72qKTMQTb8= +github.com/CycloneDX/cyclonedx-go v0.9.0/go.mod h1:NE/EWvzELOFlG6+ljX/QeMlVt9VKcTwu8u0ccsACEsw= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= @@ -59,8 +59,8 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jfrog/archiver/v3 v3.6.1 h1:LOxnkw9pOn45DzCbZNFV6K0+6dCsQ0L8mR3ZcujO5eI= github.com/jfrog/archiver/v3 v3.6.1/go.mod h1:VgR+3WZS4N+i9FaDwLZbq+jeU4B4zctXL+gL4EMzfLw= -github.com/jfrog/build-info-go v1.9.29 h1:3vJ+kbk9PpU6wjisXi9c4qISNpYkISh/NmB5mq1ZlSY= -github.com/jfrog/build-info-go v1.9.29/go.mod h1:AzFJlN/yKfKuKcSBaGy5nNmKN1xzx6+XcRWAswCTLTA= +github.com/jfrog/build-info-go v1.9.30 h1:RmK3NuDGUoyCjCNJJBlHaCm4vWzO8BLjkuAkMzj6PGE= +github.com/jfrog/build-info-go v1.9.30/go.mod h1:fxSfpp+kQ8qln/0y7pgV3tg2exWe5OM1np5FF2LlSDg= github.com/jfrog/gofrog v1.7.4 h1:on4AeWef5LJUhGCigSjTS4Ez3n9l8+NiZlXH6UYp05c= github.com/jfrog/gofrog v1.7.4/go.mod h1:jyGiCgiqSSR7k86hcUSu67XVvmvkkgWTmPsH25wI298= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= diff --git a/http/httpclient/client.go b/http/httpclient/client.go index acea6ba99..846b73ee5 100644 --- a/http/httpclient/client.go +++ b/http/httpclient/client.go @@ -296,7 +296,9 @@ func (jc *HttpClient) doUploadFile(localPath, url string, httpClientsDetails htt if localPath != "" { file, err = os.Open(localPath) defer func() { - err = errors.Join(err, errorutils.CheckError(file.Close())) + if file != nil { + err = errors.Join(err, errorutils.CheckError(file.Close())) + } }() if errorutils.CheckError(err) != nil { return nil, nil, err diff --git a/tests/jfrogclient_test.go b/tests/jfrogclient_test.go index ceda8bd8a..ab02ff733 100644 --- a/tests/jfrogclient_test.go +++ b/tests/jfrogclient_test.go @@ -65,6 +65,7 @@ func setupIntegrationTests() { createXrayWatchManager() createXrayPolicyManager() createXrayBinMgrManager() + createXrayIgnoreRuleManager() } if *TestPipelines { createPipelinesIntegrationsManager() diff --git a/tests/utils_test.go b/tests/utils_test.go index feb157850..dc3b5c98b 100644 --- a/tests/utils_test.go +++ b/tests/utils_test.go @@ -113,9 +113,10 @@ var ( testsBundleDeleteRemoteService *distributionServices.DeleteReleaseBundleService // Xray Services - testsXrayWatchService *xrayServices.WatchService - testsXrayPolicyService *xrayServices.PolicyService - testXrayBinMgrService *xrayServices.BinMgrService + testsXrayWatchService *xrayServices.WatchService + testsXrayPolicyService *xrayServices.PolicyService + testXrayBinMgrService *xrayServices.BinMgrService + testsXrayIgnoreRuleService *xrayServices.IgnoreRuleService // Pipelines Services testsPipelinesIntegrationsService *pipelinesServices.IntegrationsService @@ -480,6 +481,18 @@ func createXrayBinMgrManager() { testXrayBinMgrService.XrayDetails = xrayDetails } +func createXrayIgnoreRuleManager() { + xrayDetails := GetXrayDetails() + client, err := jfroghttpclient.JfrogClientBuilder(). + SetClientCertPath(xrayDetails.GetClientCertPath()). + SetClientCertKeyPath(xrayDetails.GetClientCertKeyPath()). + AppendPreRequestInterceptor(xrayDetails.RunPreRequestFunctions). + Build() + failOnHttpClientCreation(err) + testsXrayIgnoreRuleService = xrayServices.NewIgnoreRuleService(client) + testsXrayIgnoreRuleService.XrayDetails = xrayDetails +} + func createPipelinesIntegrationsManager() { pipelinesDetails := GetPipelinesDetails() client, err := jfroghttpclient.JfrogClientBuilder(). diff --git a/tests/xrayignorerule_test.go b/tests/xrayignorerule_test.go new file mode 100644 index 000000000..9b663f282 --- /dev/null +++ b/tests/xrayignorerule_test.go @@ -0,0 +1,124 @@ +package tests + +import ( + "fmt" + "testing" + "time" + + "github.com/jfrog/jfrog-client-go/xray/services/utils" + "github.com/stretchr/testify/assert" +) + +func TestXrayIgnoreRule(t *testing.T) { + initXrayTest(t) + t.Run("createCveIgnoreRule", createCveIgnoreRule) + t.Run("createVulnerabilitesAndLicensesIgnoreRule", createVulnerabilitesAndLicensesIgnoreRule) + t.Run("createIgnoreRuleOnWatch", createIgnoreRuleOnWatch) +} + +func deleteIgnoreRule(t *testing.T, ignoreRuleId string) { + err := testsXrayIgnoreRuleService.Delete(ignoreRuleId) + assert.NoError(t, err) +} + +func createCveIgnoreRule(t *testing.T) { + var ignoreRuleId string + defer func() { + deleteIgnoreRule(t, ignoreRuleId) + }() + + component := utils.IgnoreFilterNameVersion{ + Name: "gav://org.postgresql:postgresql", + Version: "42.2.3.jre7", + } + components := []utils.IgnoreFilterNameVersion{component} + + cve := []string{"CVE-2022-31197"} + ignoreRuleFilter := utils.IgnoreFilters{ + CVEs: cve, + Components: components, + } + + ignoreRuleId = createIgnoreRule(t, ignoreRuleFilter) + assert.NotEmpty(t, ignoreRuleId) +} + +func createVulnerabilitesAndLicensesIgnoreRule(t *testing.T) { + var ignoreRuleId string + defer func() { + deleteIgnoreRule(t, ignoreRuleId) + }() + + vulnerabilities := []string{"any"} + licenses := []string{"any"} + releaseBundle := utils.IgnoreFilterNameVersion{ + Name: "testRB", + } + releaseBundles := []utils.IgnoreFilterNameVersion{releaseBundle} + ignoreRuleFilter := utils.IgnoreFilters{ + Vulnerabilities: vulnerabilities, + Licenses: licenses, + ReleaseBundles: releaseBundles, + } + + ignoreRuleId = createIgnoreRule(t, ignoreRuleFilter) + assert.NotEmpty(t, ignoreRuleId) +} + +func createIgnoreRuleOnWatch(t *testing.T) { + cve := []string{"CVE-2022-31197"} + policyName := fmt.Sprintf("%s-%s", "test-policy-for-dummy-watch", getRunId()) + watchName := fmt.Sprintf("%s-%s", "test-watch-for-ignore-rule", getRunId()) + err := createDummyWatch(policyName, watchName) + defer func() { + assert.NoError(t, testsXrayWatchService.Delete(watchName)) + assert.NoError(t, testsXrayPolicyService.Delete(policyName)) + }() + assert.NoError(t, err) + watches := []string{watchName} + + var ignoreRuleId string + defer func() { + deleteIgnoreRule(t, ignoreRuleId) + }() + + ignoreRuleFilter := utils.IgnoreFilters{ + CVEs: cve, + Watches: watches, + } + + ignoreRuleId = createIgnoreRule(t, ignoreRuleFilter) + assert.NotEmpty(t, ignoreRuleId) +} + +func createIgnoreRule(t *testing.T, ignoreRuleFilter utils.IgnoreFilters) (ignoreRuleId string) { + ignoreRuleParams := utils.IgnoreRuleParams{ + Notes: "Create new ignore rule" + getRunId(), + ExpiresAt: time.Now().AddDate(0, 0, 1), + IgnoreFilters: ignoreRuleFilter, + } + + ignoreRuleId, err := testsXrayIgnoreRuleService.Create(ignoreRuleParams) + assert.NoError(t, err) + return ignoreRuleId +} + +func createDummyWatch(policyName string, watchName string) error { + if err := createDummyPolicy(policyName); err != nil { + return err + } + params := utils.WatchParams{ + Name: watchName, + Active: true, + Repositories: utils.WatchRepositoriesParams{ + Type: utils.WatchRepositoriesAll, + }, + Policies: []utils.AssignedPolicy{ + { + Name: policyName, + Type: "security", + }, + }, + } + return testsXrayWatchService.Create(params) +} diff --git a/utils/git.go b/utils/git.go index 62d4c73ff..b751176f8 100644 --- a/utils/git.go +++ b/utils/git.go @@ -124,7 +124,9 @@ func (m *GitManager) readUrl() { return } defer func() { - m.err = errors.Join(m.err, errorutils.CheckError(file.Close())) + if file != nil { + m.err = errors.Join(m.err, errorutils.CheckError(file.Close())) + } }() scanner := bufio.NewScanner(file) @@ -223,7 +225,9 @@ func (m *GitManager) readRevisionFromRef(refPath string) { return } defer func() { - m.err = errors.Join(m.err, errorutils.CheckError(file.Close())) + if file != nil { + m.err = errors.Join(m.err, errorutils.CheckError(file.Close())) + } }() scanner := bufio.NewScanner(file) @@ -253,7 +257,9 @@ func (m *GitManager) readRevisionFromPackedRef(ref string) { return } defer func() { - m.err = errors.Join(m.err, errorutils.CheckError(file.Close())) + if file != nil { + m.err = errors.Join(m.err, errorutils.CheckError(file.Close())) + } }() scanner := bufio.NewScanner(file) diff --git a/utils/io/content/contentwriter.go b/utils/io/content/contentwriter.go index 0dbbb89f1..e74f52c8a 100644 --- a/utils/io/content/contentwriter.go +++ b/utils/io/content/contentwriter.go @@ -108,7 +108,7 @@ func (rw *ContentWriter) run() { var err error if !rw.useStdout { defer func() { - if err = errors.Join(rw.outputFile.Sync(), rw.outputFile.Close()); err != nil { + if err = errors.Join(err, rw.outputFile.Sync(), rw.outputFile.Close()); err != nil { rw.errorsQueue.AddError(errorutils.CheckError(err)) } }() diff --git a/utils/io/fileutils/files.go b/utils/io/fileutils/files.go index d5841419c..659ad5f94 100644 --- a/utils/io/fileutils/files.go +++ b/utils/io/fileutils/files.go @@ -344,7 +344,9 @@ func GetFileDetails(filePath string, includeChecksums bool) (details *FileDetail file, err := os.Open(filePath) defer func() { - err = errors.Join(err, errorutils.CheckError(file.Close())) + if file != nil { + err = errors.Join(err, errorutils.CheckError(file.Close())) + } }() if err != nil { return nil, errorutils.CheckError(err) @@ -360,7 +362,9 @@ func GetFileDetails(filePath string, includeChecksums bool) (details *FileDetail func calcChecksumDetails(filePath string) (checksum entities.Checksum, err error) { file, err := os.Open(filePath) defer func() { - err = errors.Join(err, errorutils.CheckError(file.Close())) + if file != nil { + err = errors.Join(err, errorutils.CheckError(file.Close())) + } }() if err != nil { return entities.Checksum{}, errorutils.CheckError(err) diff --git a/utils/io/fileutils/zip.go b/utils/io/fileutils/zip.go index 753185033..3f1b19377 100644 --- a/utils/io/fileutils/zip.go +++ b/utils/io/fileutils/zip.go @@ -15,12 +15,14 @@ func ZipFolderFiles(source, target string) (err error) { return errorutils.CheckError(err) } defer func() { - err = errors.Join(errorutils.CheckError(zipFile.Close())) + if zipFile != nil { + err = errors.Join(err, errorutils.CheckError(zipFile.Close())) + } }() archive := zip.NewWriter(zipFile) defer func() { - err = errors.Join(errorutils.CheckError(archive.Close())) + err = errors.Join(err, errorutils.CheckError(archive.Close())) }() return filepath.Walk(source, func(path string, info os.FileInfo, err error) (currentErr error) { @@ -48,7 +50,9 @@ func ZipFolderFiles(source, target string) (err error) { return errorutils.CheckError(currentErr) } defer func() { - currentErr = errors.Join(errorutils.CheckError(file.Close())) + if file != nil { + currentErr = errors.Join(currentErr, errorutils.CheckError(file.Close())) + } }() _, currentErr = io.Copy(writer, file) return diff --git a/utils/utils.go b/utils/utils.go index 03bd62c39..47f2fc9bc 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -28,7 +28,7 @@ import ( const ( Development = "development" Agent = "jfrog-client-go" - Version = "1.42.0" + Version = "1.43.0" ) type MinVersionProduct string diff --git a/xray/manager.go b/xray/manager.go index fa14c5ceb..25256d90f 100644 --- a/xray/manager.go +++ b/xray/manager.go @@ -111,6 +111,30 @@ func (sm *XrayServicesManager) DeletePolicy(policyName string) error { return policyService.Delete(policyName) } +// CreatePolicy will create a new Xray ignore rule +// The function returns the ignore rule id if succeeded or empty string and error message if fails +func (sm *XrayServicesManager) CreateIgnoreRule(params utils.IgnoreRuleParams) (string, error) { + ignoreRuleService := services.NewIgnoreRuleService(sm.client) + ignoreRuleService.XrayDetails = sm.config.GetServiceDetails() + return ignoreRuleService.Create(params) +} + +// CreatePolicy will create a new Xray ignore rule +// The function returns the ignore rule id if succeeded or empty string and error message if fails +func (sm *XrayServicesManager) GetIgnoreRule(ignoreRuleId string) (*utils.IgnoreRuleParams, error) { + ignoreRuleService := services.NewIgnoreRuleService(sm.client) + ignoreRuleService.XrayDetails = sm.config.GetServiceDetails() + return ignoreRuleService.Get(ignoreRuleId) +} + +// CreatePolicy will create a new Xray ignore rule +// The function returns the ignore rule id if succeeded or empty string and error message if fails +func (sm *XrayServicesManager) DeleteIgnoreRule(ignoreRuleId string) error { + ignoreRuleService := services.NewIgnoreRuleService(sm.client) + ignoreRuleService.XrayDetails = sm.config.GetServiceDetails() + return ignoreRuleService.Delete(ignoreRuleId) +} + // AddBuildsToIndexing will add builds to Xray indexing configuration func (sm *XrayServicesManager) AddBuildsToIndexing(buildNames []string) error { binMgrService := services.NewBinMgrService(sm.client) diff --git a/xray/services/ignorerule.go b/xray/services/ignorerule.go new file mode 100644 index 000000000..440ca6ffe --- /dev/null +++ b/xray/services/ignorerule.go @@ -0,0 +1,139 @@ +package services + +import ( + "encoding/json" + "fmt" + "net/http" + "regexp" + + artUtils "github.com/jfrog/jfrog-client-go/artifactory/services/utils" + "github.com/jfrog/jfrog-client-go/auth" + "github.com/jfrog/jfrog-client-go/http/jfroghttpclient" + clientutils "github.com/jfrog/jfrog-client-go/utils" + "github.com/jfrog/jfrog-client-go/utils/errorutils" + "github.com/jfrog/jfrog-client-go/utils/log" + "github.com/jfrog/jfrog-client-go/xray/services/utils" +) + +const ( + ignoreRuleAPIURL = "api/v1/ignore_rules" +) + +// IgnoreRuleService defines the http client and Xray details +type IgnoreRuleService struct { + client *jfroghttpclient.JfrogHttpClient + XrayDetails auth.ServiceDetails +} + +// NewIgnoreRuleService creates a new Xray Ignore Rule Service +func NewIgnoreRuleService(client *jfroghttpclient.JfrogHttpClient) *IgnoreRuleService { + return &IgnoreRuleService{client: client} +} + +// GetXrayDetails returns the Xray details +func (xirs *IgnoreRuleService) GetXrayDetails() auth.ServiceDetails { + return xirs.XrayDetails +} + +// GetJfrogHttpClient returns the http client +func (xirs *IgnoreRuleService) GetJfrogHttpClient() *jfroghttpclient.JfrogHttpClient { + return xirs.client +} + +// The getIgnoreRuleURL does not end with a slash +// So, calling functions will need to add it +func (xirs *IgnoreRuleService) getIgnoreRuleURL() string { + return clientutils.AddTrailingSlashIfNeeded(xirs.XrayDetails.GetUrl()) + ignoreRuleAPIURL +} + +// Delete will delete an ignore rule by id +func (xirs *IgnoreRuleService) Delete(ignoreRuleId string) error { + httpClientsDetails := xirs.XrayDetails.CreateHttpClientDetails() + artUtils.SetContentType("application/json", &httpClientsDetails.Headers) + + log.Info("Deleting ignore rule...") + resp, body, err := xirs.client.SendDelete(xirs.getIgnoreRuleURL()+"/"+ignoreRuleId, nil, &httpClientsDetails) + if err != nil { + return err + } + if err = errorutils.CheckResponseStatusWithBody(resp, body, http.StatusNoContent); err != nil { + return err + } + log.Debug("Xray response status:", resp.Status) + log.Info("Done deleting ignore rule.") + return nil +} + +// Create will create a new Xray ignore rule +// The function creates the ignore rule and returns its id which is recieved after post +func (xirs *IgnoreRuleService) Create(params utils.IgnoreRuleParams) (ignoreRuleId string, err error) { + ignoreRuleBody := utils.CreateIgnoreRuleBody(params) + content, err := json.Marshal(ignoreRuleBody) + if err != nil { + return "", errorutils.CheckError(err) + } + + httpClientsDetails := xirs.XrayDetails.CreateHttpClientDetails() + artUtils.SetContentType("application/json", &httpClientsDetails.Headers) + var url = xirs.getIgnoreRuleURL() + + log.Info("Create new ignore rule...") + resp, body, err := xirs.client.SendPost(url, content, &httpClientsDetails) + if err != nil { + return "", err + } + + if err = errorutils.CheckResponseStatusWithBody(resp, body, http.StatusCreated); err != nil { + return "", err + } + log.Debug("Xray response status:", resp.Status) + + ignoreRuleId, err = getIgnoreRuleIdFromBody(body) + if err != nil { + return "", err + } + + log.Info("Done creating ignore rule.") + log.Debug("Ignore rule id is: ", ignoreRuleId) + + return ignoreRuleId, nil +} + +func getIgnoreRuleIdFromBody(body []byte) (string, error) { + str := string(body) + + re := regexp.MustCompile(`id:\s*([a-f0-9-]+)`) + match := re.FindStringSubmatch(str) + + if len(match) <= 1 { + return "", errorutils.CheckErrorf("couldn't find id for ignore rule in str: %s", str) + } + + return match[1], nil +} + +// Get retrieves the details about an Xray ignore rule by its id +// It will error if the ignore rule id can't be found. +func (xirs *IgnoreRuleService) Get(ignoreRuleId string) (ignoreRuleResp *utils.IgnoreRuleParams, err error) { + httpClientsDetails := xirs.XrayDetails.CreateHttpClientDetails() + log.Info(fmt.Sprintf("Getting ignore rule '%s'...", ignoreRuleId)) + resp, body, _, err := xirs.client.SendGet(xirs.getIgnoreRuleURL()+"/"+ignoreRuleId, true, &httpClientsDetails) + ignoreRule := &utils.IgnoreRuleBody{} + + if err != nil { + return &utils.IgnoreRuleParams{}, err + } + if err = errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK); err != nil { + log.Debug("Xray response:", string(body), resp.Status) + return &utils.IgnoreRuleParams{}, err + } + + if err = json.Unmarshal(body, ignoreRule); err != nil { + return &utils.IgnoreRuleParams{}, errorutils.CheckErrorf("failed unmarshalling %s for ignore rule %s", string(body), ignoreRuleId) + } + + log.Debug("Xray response status:", resp.Status) + log.Info("Done getting ignore rule.") + + return &ignoreRule.IgnoreRuleParams, nil +} diff --git a/xray/services/utils/ignorerulebody.go b/xray/services/utils/ignorerulebody.go new file mode 100644 index 000000000..4b465a162 --- /dev/null +++ b/xray/services/utils/ignorerulebody.go @@ -0,0 +1,65 @@ +package utils + +import "time" + +type IgnoreRuleParams struct { + Notes string `json:"notes"` + ExpiresAt time.Time `json:"expires_at,omitempty"` + IgnoreFilters IgnoreFilters `json:"ignore_filters"` +} + +type IgnoreRuleBody struct { + Id string `json:"id,omitempty"` + Author string `json:"author,omitempty"` + Created time.Time `json:"created,omitempty"` + IsExpired bool `json:"is_expired,omitempty"` + IgnoreRuleParams +} + +type IgnoreFilters struct { + Vulnerabilities []string `json:"vulnerabilities,omitempty"` + Licenses []string `json:"licenses,omitempty"` + CVEs []string `json:"cves,omitempty"` + Policies []string `json:"policies,omitempty"` + Watches []string `json:"watches,omitempty"` + DockerLayers []string `json:"docker-layers,omitempty"` + OperationalRisks []string `json:"operational_risk,omitempty"` + Exposures []ExposuresFilterName `json:"exposures,omitempty"` + ReleaseBundles []IgnoreFilterNameVersion `json:"release-bundles,omitempty"` + Builds []IgnoreFilterNameVersion `json:"builds,omitempty"` + Components []IgnoreFilterNameVersion `json:"components,omitempty"` + Artifacts []IgnoreFilterNameVersionPath `json:"artifacts,omitempty"` +} + +type IgnoreFilterNameVersion struct { + Name string `json:"name"` + Version string `json:"version,omitempty"` +} + +type IgnoreFilterNameVersionPath struct { + IgnoreFilterNameVersion + Path string `json:"path,omitempty"` +} + +type ExposuresFilterName struct { + Catagories []ExposuresCatagories `json:"catagories,omitempty"` + Scanners []string `json:"scanners,omitempty"` + FilePath []string `json:"file_path,omitempty"` +} + +type ExposuresCatagories struct { + Secrets bool `json:"secrets,omitempty"` + Services bool `json:"services,omitempty"` + Applications bool `json:"applications,omitempty"` + Iac bool `json:"iac,omitempty"` +} + +func NewIgnoreRuleParams() IgnoreRuleParams { + return IgnoreRuleParams{} +} + +func CreateIgnoreRuleBody(ignoreRuleParams IgnoreRuleParams) IgnoreRuleBody { + return IgnoreRuleBody{ + IgnoreRuleParams: ignoreRuleParams, + } +}