diff --git a/artifactory/commands/golang/go.go b/artifactory/commands/golang/go.go index 6395c5b0c..93ce330e0 100644 --- a/artifactory/commands/golang/go.go +++ b/artifactory/commands/golang/go.go @@ -16,9 +16,7 @@ import ( "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/io/fileutils" "github.com/jfrog/jfrog-client-go/utils/log" - "io/fs" "net/http" - "os" "path" "path/filepath" "strings" @@ -221,16 +219,7 @@ func copyGoPackageFiles(destPath, packageName, rtTargetRepo string, authArtDetai return fmt.Errorf("couldn't find suitable package files: %s", packageFilesPath) } // Set permission recursively - return filepath.WalkDir(destPath, func(path string, info fs.DirEntry, err error) error { - if err != nil { - return err - } - err = os.Chmod(path, 0700) - if err != nil { - return err - } - return nil - }) + return coreutils.SetPermissionsRecursively(destPath, 0700) } // getPackageFilePathFromArtifactory returns a string that represents the package files cache path. diff --git a/artifactory/commands/transferfiles/delayedartifactshandler.go b/artifactory/commands/transferfiles/delayedartifactshandler.go index 4609b63fb..adab43f5c 100644 --- a/artifactory/commands/transferfiles/delayedartifactshandler.go +++ b/artifactory/commands/transferfiles/delayedartifactshandler.go @@ -220,7 +220,7 @@ func getDelayUploadComparisonFunctions(packageType string) []shouldDelayUpload { switch packageType { case maven, gradle, ivy: return []shouldDelayUpload{func(fileName string) bool { - return filepath.Ext(fileName) == ".pom" + return filepath.Ext(fileName) == ".pom" || fileName == "pom.xml" }} case docker: return []shouldDelayUpload{func(fileName string) bool { diff --git a/artifactory/commands/transferfiles/state/transfersnapshot.go b/artifactory/commands/transferfiles/state/transfersnapshot.go index 2cc04782f..d51b551a1 100644 --- a/artifactory/commands/transferfiles/state/transfersnapshot.go +++ b/artifactory/commands/transferfiles/state/transfersnapshot.go @@ -1,12 +1,15 @@ package state import ( - "github.com/jfrog/jfrog-cli-core/v2/utils/reposnapshot" - "github.com/jfrog/jfrog-client-go/utils/errorutils" "sync" "time" + + "github.com/jfrog/jfrog-cli-core/v2/utils/reposnapshot" + "github.com/jfrog/jfrog-client-go/utils/errorutils" ) +var saveRepoSnapshotMutex sync.Mutex + type SnapshotActionFunc func(rts *RepoTransferSnapshot) error var snapshotSaveIntervalMin = snapshotSaveIntervalMinDefault @@ -20,8 +23,7 @@ type RepoTransferSnapshot struct { snapshotManager reposnapshot.RepoSnapshotManager lastSaveTimestamp time.Time // This boolean marks that this snapshot continues a previous run. It allows skipping certain checks if it was not loaded, because some data is known to be new. - loadedFromSnapshot bool - saveRepoSnapshotMutex sync.Mutex + loadedFromSnapshot bool } // Runs the provided action on the snapshot manager, and periodically saves the repo state and snapshot to the snapshot dir. @@ -38,22 +40,16 @@ func (ts *TransferStateManager) snapshotAction(action SnapshotActionFunc) (err e return nil } - if !ts.repoTransferSnapshot.saveRepoSnapshotMutex.TryLock() { + if !saveRepoSnapshotMutex.TryLock() { return nil } - defer ts.repoTransferSnapshot.saveRepoSnapshotMutex.Unlock() + defer saveRepoSnapshotMutex.Unlock() ts.repoTransferSnapshot.lastSaveTimestamp = now if err = ts.repoTransferSnapshot.snapshotManager.PersistRepoSnapshot(); err != nil { return err } - return saveStateToSnapshot(&ts.TransferState) -} - -func saveStateToSnapshot(ts *TransferState) error { - saveStateMutex.Lock() - defer saveStateMutex.Unlock() return ts.persistTransferState(true) } diff --git a/artifactory/utils/dependenciesutils.go b/artifactory/utils/dependenciesutils.go index 790fd01fe..91fa38cd1 100644 --- a/artifactory/utils/dependenciesutils.go +++ b/artifactory/utils/dependenciesutils.go @@ -57,7 +57,7 @@ func DownloadAnalyzerManagerIfNeeded() error { downloadUrl := artDetails.ArtifactoryUrl + remotePath remoteFileDetails, _, err := client.GetRemoteFileDetails(downloadUrl, &httpClientDetails) if err != nil { - return err + return fmt.Errorf("couldn't get remote file details for %s: %s", downloadUrl, err.Error()) } analyzerManagerDir, err := xrayutils.GetAnalyzerManagerDirAbsolutePath() if err != nil { @@ -70,7 +70,8 @@ func DownloadAnalyzerManagerIfNeeded() error { return err } if exist { - sha2, err := fileutils.ReadFile(checksumFilePath) + var sha2 []byte + sha2, err = fileutils.ReadFile(checksumFilePath) if err != nil { return err } @@ -84,17 +85,6 @@ func DownloadAnalyzerManagerIfNeeded() error { if err = DownloadDependency(artDetails, remotePath, filepath.Join(analyzerManagerDir, xrayutils.AnalyzerManagerZipName), true); err != nil { return err } - // Add permission for all unzipped files - filesList, err := fileutils.ListFilesRecursiveWalkIntoDirSymlink(analyzerManagerDir, false) - if err != nil { - return err - } - for _, file := range filesList { - if err = os.Chmod(file, 0777); err != nil { - return errorutils.CheckError(err) - } - } - return createChecksumFile(checksumFilePath, remoteFileDetails.Checksum.Sha256) } @@ -219,9 +209,13 @@ func DownloadDependency(artDetails *config.ServerDetails, downloadPath, targetPa return err } resp, err := client.DownloadFile(downloadFileDetails, "", &httpClientDetails, shouldExplode, false) - if err == nil && resp.StatusCode != http.StatusOK { - err = errorutils.CheckErrorf(resp.Status + " received when attempting to download " + downloadUrl) + if err != nil { + err = errorutils.CheckErrorf("received error while attempting to download '%s': %s"+downloadUrl, err.Error()) + } + if err = errorutils.CheckResponseStatus(resp, http.StatusOK); err != nil { + return err } + err = coreutils.SetPermissionsRecursively(tempDirPath, 0700) if err != nil { return err } diff --git a/go.mod b/go.mod index 891def452..2f2afd68e 100644 --- a/go.mod +++ b/go.mod @@ -12,9 +12,9 @@ require ( github.com/google/uuid v1.3.1 github.com/gookit/color v1.5.4 github.com/jedib0t/go-pretty/v6 v6.4.7 - github.com/jfrog/build-info-go v1.9.9 + github.com/jfrog/build-info-go v1.9.10 github.com/jfrog/gofrog v1.3.0 - github.com/jfrog/jfrog-client-go v1.31.6 + github.com/jfrog/jfrog-client-go v1.32.2 github.com/magiconair/properties v1.8.7 github.com/manifoldco/promptui v0.9.0 github.com/owenrumney/go-sarif/v2 v2.2.0 @@ -23,13 +23,12 @@ require ( github.com/stretchr/testify v1.8.4 github.com/urfave/cli v1.22.14 github.com/vbauerster/mpb/v7 v7.5.3 - golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 golang.org/x/mod v0.12.0 golang.org/x/sync v0.3.0 - golang.org/x/term v0.11.0 - golang.org/x/text v0.12.0 + golang.org/x/term v0.12.0 + golang.org/x/text v0.13.0 gopkg.in/yaml.v3 v3.0.1 - ) require ( @@ -44,13 +43,14 @@ require ( github.com/andybalholm/brotli v1.0.1 // indirect github.com/cloudflare/circl v1.3.3 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-git/go-billy/v5 v5.4.1 // indirect - github.com/go-git/go-git/v5 v5.8.1 // indirect + github.com/go-git/go-billy/v5 v5.5.0 // indirect + github.com/go-git/go-git/v5 v5.9.0 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/snappy v0.0.2 // indirect @@ -86,16 +86,10 @@ require ( github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect - golang.org/x/crypto v0.12.0 // indirect - golang.org/x/net v0.14.0 // indirect - golang.org/x/sys v0.11.0 // indirect - golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect + golang.org/x/crypto v0.13.0 // indirect + golang.org/x/net v0.15.0 // indirect + golang.org/x/sys v0.12.0 // indirect + golang.org/x/tools v0.13.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect ) - -replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20230831152946-6ed2ae1aa57f - -replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go v1.8.9-0.20230905120411-62d1bdd4eb38 - -// replace github.com/jfrog/gofrog => github.com/jfrog/gofrog v1.2.6-0.20230418122323-2bf299dd6d27 diff --git a/go.sum b/go.sum index a028a4cfc..02c23bd61 100644 --- a/go.sum +++ b/go.sum @@ -84,14 +84,15 @@ github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnht github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY= github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= -github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0= +github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -108,11 +109,11 @@ github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbS github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4= -github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= +github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= +github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8= -github.com/go-git/go-git/v5 v5.8.1 h1:Zo79E4p7TRk0xoRgMq0RShiTHGKcKI4+DI6BfJc/Q+A= -github.com/go-git/go-git/v5 v5.8.1/go.mod h1:FHFuoD6yGz5OSKEBK+aWN9Oah0q54Jxl0abmj6GnqAo= +github.com/go-git/go-git/v5 v5.9.0 h1:cD9SFA7sHVRdJ7AYck1ZaAa/yeuBvGPxwXDL8cxrObY= +github.com/go-git/go-git/v5 v5.9.0/go.mod h1:RKIqga24sWdMGZF+1Ekv9kylsDz6LzdTSI2s/OsZWE0= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -194,12 +195,12 @@ 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/jedib0t/go-pretty/v6 v6.4.7 h1:lwiTJr1DEkAgzljsUsORmWsVn5MQjt1BPJdPCtJ6KXE= github.com/jedib0t/go-pretty/v6 v6.4.7/go.mod h1:Ndk3ase2CkQbXLLNf5QDHoYb6J9WtVfmHZu9n8rk2xs= -github.com/jfrog/build-info-go v1.8.9-0.20230905120411-62d1bdd4eb38 h1:XyAcwWP2a6a5RL861gkfgQ7MUaQ7mmDkUVoD6kMtUtQ= -github.com/jfrog/build-info-go v1.8.9-0.20230905120411-62d1bdd4eb38/go.mod h1:QEskae5fQpjeY2PBzsjWtUQVskYSNDF2sSmw/Gx44dQ= +github.com/jfrog/build-info-go v1.9.10 h1:uXnDLVxpqxoAMpXcki00QaBB+M2BoGMMpHODPkmmYOY= +github.com/jfrog/build-info-go v1.9.10/go.mod h1:ujJ8XQZMdT2tMkLSMJNyDd1pCY+duwHdjV+9or9FLIg= github.com/jfrog/gofrog v1.3.0 h1:o4zgsBZE4QyDbz2M7D4K6fXPTBJht+8lE87mS9bw7Gk= github.com/jfrog/gofrog v1.3.0/go.mod h1:IFMc+V/yf7rA5WZ74CSbXe+Lgf0iApEQLxRZVzKRUR0= -github.com/jfrog/jfrog-client-go v1.28.1-0.20230831152946-6ed2ae1aa57f h1:S6l0o2sKFLRJ+QYVB5U/PJhrnwFSmKFFY7eHpRPRH8A= -github.com/jfrog/jfrog-client-go v1.28.1-0.20230831152946-6ed2ae1aa57f/go.mod h1:uUnMrqHX7Xi+OCaZEE4b3BtsmGeOSCB7XqaEWVXEH/E= +github.com/jfrog/jfrog-client-go v1.32.2 h1:t0ceWCtFri+xsa0D2ESqD/itcovlxBXCky1A1MJ4P2I= +github.com/jfrog/jfrog-client-go v1.32.2/go.mod h1:UewnwkIf/77HzBgwCPzOHZCK6V/Nw5/JwdzN/tRb4aU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= @@ -215,12 +216,10 @@ github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= @@ -248,9 +247,9 @@ github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dz github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ= github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/owenrumney/go-sarif v1.1.1/go.mod h1:dNDiPlF04ESR/6fHlPyq7gHKmrM0sHUvAGjsoh8ZH0U= github.com/owenrumney/go-sarif/v2 v2.2.0 h1:1DmZaijK0HBZCR1fgcDSGa7VzYkU9NDmbZ7qC2QfUjE= github.com/owenrumney/go-sarif/v2 v2.2.0/go.mod h1:MSqMMx9WqlBSY7pXoOZWgEsVB4FDNfhcaXDA1j6Sr+w= @@ -275,7 +274,7 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= @@ -350,8 +349,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -362,8 +361,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= -golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -427,8 +426,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -511,15 +510,15 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= -golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -532,8 +531,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -586,8 +585,8 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E= -golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -684,7 +683,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= diff --git a/utils/config/tokenrefresh.go b/utils/config/tokenrefresh.go index 31ed2433e..298a3917d 100644 --- a/utils/config/tokenrefresh.go +++ b/utils/config/tokenrefresh.go @@ -31,11 +31,11 @@ const ( type TokenType string func AccessTokenRefreshPreRequestInterceptor(fields *auth.CommonConfigFields, httpClientDetails *httputils.HttpClientDetails) (err error) { - return tokenRefreshPreRequestInterceptor(fields, httpClientDetails, AccessToken, auth.InviteRefreshBeforeExpiryMinutes) + return tokenRefreshPreRequestInterceptor(fields, httpClientDetails, AccessToken, auth.RefreshPlatformTokenBeforeExpiryMinutes) } func ArtifactoryTokenRefreshPreRequestInterceptor(fields *auth.CommonConfigFields, httpClientDetails *httputils.HttpClientDetails) (err error) { - return tokenRefreshPreRequestInterceptor(fields, httpClientDetails, ArtifactoryToken, auth.RefreshBeforeExpiryMinutes) + return tokenRefreshPreRequestInterceptor(fields, httpClientDetails, ArtifactoryToken, auth.RefreshArtifactoryTokenBeforeExpiryMinutes) } func tokenRefreshPreRequestInterceptor(fields *auth.CommonConfigFields, httpClientDetails *httputils.HttpClientDetails, tokenType TokenType, refreshBeforeExpiryMinutes int64) (err error) { diff --git a/utils/coreutils/techutils.go b/utils/coreutils/techutils.go index bba09ba3f..26a11eeed 100644 --- a/utils/coreutils/techutils.go +++ b/utils/coreutils/techutils.go @@ -189,7 +189,7 @@ func DetectedTechnologiesList() (technologies []string) { return } techStringsList := DetectedTechnologiesToSlice(detectedTechnologies) - log.Info(fmt.Sprintf("Detected: %s.", strings.Join(techStringsList, ","))) + log.Info(fmt.Sprintf("Detected: %s.", strings.Join(techStringsList, ", "))) return techStringsList } diff --git a/utils/coreutils/utils.go b/utils/coreutils/utils.go index 15bb5809f..463a65d50 100644 --- a/utils/coreutils/utils.go +++ b/utils/coreutils/utils.go @@ -4,6 +4,7 @@ import ( "bytes" "errors" "fmt" + "io/fs" "os" "os/exec" "path/filepath" @@ -599,3 +600,20 @@ func GetMaskedCommandString(cmd *exec.Cmd) string { } return cmdString } + +func SetPermissionsRecursively(dirPath string, mode os.FileMode) error { + err := filepath.WalkDir(dirPath, func(path string, info fs.DirEntry, e error) error { + if e != nil { + return e + } + e = os.Chmod(path, mode) + if e != nil { + return e + } + return nil + }) + if err != nil { + return errorutils.CheckErrorf("failed while setting permission to '%s' files: %s", dirPath, err.Error()) + } + return nil +} diff --git a/utils/usage/usage.go b/utils/usage/usage.go index ff9660bb2..30f40f18c 100644 --- a/utils/usage/usage.go +++ b/utils/usage/usage.go @@ -2,8 +2,6 @@ package usage import ( "fmt" - xrayutils "github.com/jfrog/jfrog-cli-core/v2/xray/utils" - "golang.org/x/sync/errgroup" "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" @@ -86,18 +84,19 @@ func (ur *UsageReporter) Report(features ...ReportFeature) { } log.Debug(ReportUsagePrefix, "Sending info...") if ur.sendToEcosystem { - ur.reportWaitGroup.Go(func() error { - return ur.reportToEcosystem(features...) - }) - } - if ur.sendToXray { - ur.reportWaitGroup.Go(func() error { - return ur.reportToXray(features...) + ur.reportWaitGroup.Go(func() (err error) { + if err = ur.reportToEcosystem(features...); err != nil { + err = fmt.Errorf("ecosystem, %s", err.Error()) + } + return }) } if ur.sendToArtifactory { - ur.reportWaitGroup.Go(func() error { - return ur.reportToArtifactory(features...) + ur.reportWaitGroup.Go(func() (err error) { + if err = ur.reportToArtifactory(features...); err != nil { + err = fmt.Errorf("artifactory, %s", err.Error()) + } + return }) } } @@ -122,23 +121,6 @@ func (ur *UsageReporter) reportToEcosystem(features ...ReportFeature) (err error return ecosysusage.SendEcosystemUsageReports(reports...) } -func (ur *UsageReporter) reportToXray(features ...ReportFeature) (err error) { - if ur.serverDetails.XrayUrl == "" { - err = errorutils.CheckErrorf("Xray Url is not set.") - return - } - serviceManager, err := xrayutils.CreateXrayServiceManager(ur.serverDetails) - if err != nil { - return - } - events := ur.convertAttributesToXrayEvents(features...) - if len(events) == 0 { - err = errorutils.CheckErrorf("Nothing to send.") - return - } - return xrayusage.SendXrayUsageEvents(*serviceManager, events...) -} - func (ur *UsageReporter) reportToArtifactory(features ...ReportFeature) (err error) { if ur.serverDetails.ArtifactoryUrl == "" { err = errorutils.CheckErrorf("Artifactory Url is not set..") diff --git a/utils/usage/usage_test.go b/utils/usage/usage_test.go index d67ceef25..657f19ba1 100644 --- a/utils/usage/usage_test.go +++ b/utils/usage/usage_test.go @@ -159,64 +159,6 @@ func createArtifactoryUsageHandler(t *testing.T, productName, commandName string } } -func TestReportXrayUsage(t *testing.T) { - const productName = "test-product" - const commandName = "test-command" - const clientName = "test-client" - - server := httptest.NewServer(createXrayUsageHandler(t, productName, commandName, clientName)) - defer server.Close() - serverDetails := &config.ServerDetails{XrayUrl: server.URL + "/"} - - reporter := NewUsageReporter(productName, serverDetails).SetSendToEcosystem(false).SetSendToArtifactory(false) - - reporter.Report(ReportFeature{ - FeatureId: commandName, - ClientId: clientName, - }) - assert.NoError(t, reporter.WaitForResponses()) -} - -func TestReportXrayError(t *testing.T) { - reporter := NewUsageReporter("", &config.ServerDetails{}).SetSendToEcosystem(false).SetSendToArtifactory(false) - reporter.Report(ReportFeature{ - FeatureId: "", - }) - assert.Error(t, reporter.WaitForResponses()) - - server := httptest.NewServer(create404UsageHandler(t)) - defer server.Close() - reporter = NewUsageReporter("", &config.ServerDetails{ArtifactoryUrl: server.URL + "/"}).SetSendToEcosystem(false).SetSendToArtifactory(false) - reporter.Report(ReportFeature{ - FeatureId: "", - }) - assert.Error(t, reporter.WaitForResponses()) -} - -func createXrayUsageHandler(t *testing.T, productId, commandName, clientId string) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - if r.RequestURI == "/api/v1/system/version" { - w.WriteHeader(http.StatusOK) - _, err := w.Write([]byte(`{"xray_version":"6.9.0"}`)) - assert.NoError(t, err) - return - } - if r.RequestURI == "/api/v1/usage/events/send" { - // Check request - buf := new(bytes.Buffer) - _, err := buf.ReadFrom(r.Body) - assert.NoError(t, err) - featureId := xrayusage.GetExpectedXrayEventName(productId, commandName) - assert.Equal(t, fmt.Sprintf(`[{"data":{"clientId":"%s"},"product_name":"%s","event_name":"%s","origin":"API_CLI"}]`, clientId, productId, featureId), buf.String()) - - // Send response OK - w.WriteHeader(http.StatusOK) - _, err = w.Write([]byte("{}")) - assert.NoError(t, err) - } - } -} - func TestReportEcosystemUsageError(t *testing.T) { // No features reporter := NewUsageReporter("", &config.ServerDetails{}).SetSendToArtifactory(false).SetSendToXray(false) diff --git a/xray/commands/audit/audit.go b/xray/commands/audit/audit.go index 6f3f47c90..5b7cd7d77 100644 --- a/xray/commands/audit/audit.go +++ b/xray/commands/audit/audit.go @@ -91,7 +91,8 @@ func (auditCmd *AuditCommand) Run() (err error) { SetWorkingDirs(workingDirs). SetMinSeverityFilter(auditCmd.minSeverityFilter). SetFixableOnly(auditCmd.fixableOnly). - SetGraphBasicParams(auditCmd.AuditBasicParams) + SetGraphBasicParams(auditCmd.AuditBasicParams). + SetThirdPartyApplicabilityScan(auditCmd.thirdPartyApplicabilityScan) auditResults, err := RunAudit(auditParams) if err != nil { return @@ -157,13 +158,13 @@ func RunAudit(auditParams *AuditParams) (results *Results, err error) { return } var xrayManager *xray.XrayServicesManager - xrayManager, auditParams.xrayVersion, err = xrayutils.CreateXrayServiceManagerAndGetVersion(serverDetails) - if err != nil { + if xrayManager, auditParams.xrayVersion, err = xrayutils.CreateXrayServiceManagerAndGetVersion(serverDetails); err != nil { return } if err = clientutils.ValidateMinimumVersion(clientutils.Xray, auditParams.xrayVersion, scangraph.GraphScanMinXrayVersion); err != nil { return } + results.ExtendedScanResults.XrayVersion = auditParams.xrayVersion results.ExtendedScanResults.EntitledForJas, err = isEntitledForJas(xrayManager, auditParams.xrayVersion) if err != nil { return @@ -180,12 +181,12 @@ func RunAudit(auditParams *AuditParams) (results *Results, err error) { // Wait for the Download of the AnalyzerManager to complete. if err = errGroup.Wait(); err != nil { - return + err = errors.New("failed while trying to get Analyzer Manager: " + err.Error()) } // Run scanners only if the user is entitled for Advanced Security if results.ExtendedScanResults.EntitledForJas { - results.JasError = runJasScannersAndSetResults(results.ExtendedScanResults, auditParams.DirectDependencies(), serverDetails, auditParams.workingDirs, auditParams.Progress()) + results.JasError = runJasScannersAndSetResults(results.ExtendedScanResults, auditParams.DirectDependencies(), serverDetails, auditParams.workingDirs, auditParams.Progress(), auditParams.xrayGraphScanParams.MultiScanId, auditParams.thirdPartyApplicabilityScan) } return } diff --git a/xray/commands/audit/auditparams.go b/xray/commands/audit/auditparams.go index 69c83a55f..9dc42e3c2 100644 --- a/xray/commands/audit/auditparams.go +++ b/xray/commands/audit/auditparams.go @@ -13,6 +13,8 @@ type AuditParams struct { minSeverityFilter string *xrayutils.AuditBasicParams xrayVersion string + // Include third party dependencies source code in the applicability scan. + thirdPartyApplicabilityScan bool } func NewAuditParams() *AuditParams { @@ -75,3 +77,8 @@ func (params *AuditParams) SetMinSeverityFilter(minSeverityFilter string) *Audit params.minSeverityFilter = minSeverityFilter return params } + +func (params *AuditParams) SetThirdPartyApplicabilityScan(includeThirdPartyDeps bool) *AuditParams { + params.thirdPartyApplicabilityScan = includeThirdPartyDeps + return params +} diff --git a/xray/commands/audit/jas/applicability/applicabilitymanager.go b/xray/commands/audit/jas/applicability/applicabilitymanager.go index cbab69833..b038e4371 100644 --- a/xray/commands/audit/jas/applicability/applicabilitymanager.go +++ b/xray/commands/audit/jas/applicability/applicabilitymanager.go @@ -1,14 +1,13 @@ package applicability import ( - "github.com/jfrog/jfrog-cli-core/v2/xray/commands/audit/jas" "path/filepath" - "strings" + + "github.com/jfrog/jfrog-cli-core/v2/xray/commands/audit/jas" "github.com/jfrog/gofrog/datastructures" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" "github.com/jfrog/jfrog-cli-core/v2/xray/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" "github.com/owenrumney/go-sarif/v2/sarif" @@ -22,10 +21,11 @@ const ( ) type ApplicabilityScanManager struct { - applicabilityScanResults map[string]utils.ApplicabilityStatus + applicabilityScanResults []*sarif.Run directDependenciesCves []string xrayResults []services.ScanResponse scanner *jas.JasScanner + thirdPartyScan bool } // The getApplicabilityScanResults function runs the applicability scan flow, which includes the following steps: @@ -38,8 +38,8 @@ type ApplicabilityScanManager struct { // bool: true if the user is entitled to the applicability scan, false otherwise. // error: An error object (if any). func RunApplicabilityScan(xrayResults []services.ScanResponse, directDependencies []string, - scannedTechnologies []coreutils.Technology, scanner *jas.JasScanner) (results map[string]utils.ApplicabilityStatus, err error) { - applicabilityScanManager := newApplicabilityScanManager(xrayResults, directDependencies, scanner) + scannedTechnologies []coreutils.Technology, scanner *jas.JasScanner, thirdPartyContextualAnalysis bool) (results []*sarif.Run, err error) { + applicabilityScanManager := newApplicabilityScanManager(xrayResults, directDependencies, scanner, thirdPartyContextualAnalysis) if !applicabilityScanManager.shouldRunApplicabilityScan(scannedTechnologies) { log.Debug("The technologies that have been scanned are currently not supported for contextual analysis scanning, or we couldn't find any vulnerable direct dependencies. Skipping....") return @@ -52,13 +52,14 @@ func RunApplicabilityScan(xrayResults []services.ScanResponse, directDependencie return } -func newApplicabilityScanManager(xrayScanResults []services.ScanResponse, directDependencies []string, scanner *jas.JasScanner) (manager *ApplicabilityScanManager) { +func newApplicabilityScanManager(xrayScanResults []services.ScanResponse, directDependencies []string, scanner *jas.JasScanner, thirdPartyScan bool) (manager *ApplicabilityScanManager) { directDependenciesCves := extractDirectDependenciesCvesFromScan(xrayScanResults, directDependencies) return &ApplicabilityScanManager{ - applicabilityScanResults: map[string]utils.ApplicabilityStatus{}, + applicabilityScanResults: []*sarif.Run{}, directDependenciesCves: directDependenciesCves, xrayResults: xrayScanResults, scanner: scanner, + thirdPartyScan: thirdPartyScan, } } @@ -111,13 +112,11 @@ func (asm *ApplicabilityScanManager) Run(wd string) (err error) { if err = asm.runAnalyzerManager(); err != nil { return } - var workingDirResults map[string]utils.ApplicabilityStatus - if workingDirResults, err = asm.getScanResults(); err != nil { + workingDirResults, err := jas.ReadJasScanRunsFromFile(asm.scanner.ResultsFileName, wd) + if err != nil { return } - for cve, result := range workingDirResults { - asm.applicabilityScanResults[cve] = result - } + asm.applicabilityScanResults = append(asm.applicabilityScanResults, workingDirResults...) return } @@ -143,6 +142,11 @@ type scanConfiguration struct { } func (asm *ApplicabilityScanManager) createConfigFile(workingDir string) error { + skipDirs := jas.SkippedDirs + if asm.thirdPartyScan { + log.Info("Including node modules folder in applicability scan") + skipDirs = removeElementFromSlice(skipDirs, jas.NodeModulesPattern) + } configFileContent := applicabilityScanConfig{ Scans: []scanConfiguration{ { @@ -151,7 +155,7 @@ func (asm *ApplicabilityScanManager) createConfigFile(workingDir string) error { Type: applicabilityScanType, GrepDisable: false, CveWhitelist: asm.directDependenciesCves, - SkippedDirs: jas.SkippedDirs, + SkippedDirs: skipDirs, }, }, } @@ -164,36 +168,10 @@ func (asm *ApplicabilityScanManager) runAnalyzerManager() error { return asm.scanner.AnalyzerManager.Exec(asm.scanner.ConfigFileName, applicabilityScanCommand, filepath.Dir(asm.scanner.AnalyzerManager.AnalyzerManagerFullPath), asm.scanner.ServerDetails) } -func (asm *ApplicabilityScanManager) getScanResults() (applicabilityResults map[string]utils.ApplicabilityStatus, err error) { - applicabilityResults = make(map[string]utils.ApplicabilityStatus, len(asm.directDependenciesCves)) - for _, cve := range asm.directDependenciesCves { - applicabilityResults[cve] = utils.ApplicabilityUndetermined - } - - report, err := sarif.Open(asm.scanner.ResultsFileName) - if errorutils.CheckError(err) != nil || len(report.Runs) == 0 { - return - } - // Applicability results contains one run only - for _, sarifResult := range report.Runs[0].Results { - cve := getCveFromRuleId(*sarifResult.RuleID) - if _, exists := applicabilityResults[cve]; !exists { - err = errorutils.CheckErrorf("received unexpected CVE: '%s' from RuleID: '%s' that does not exists on the requested CVEs list", cve, *sarifResult.RuleID) - return - } - applicabilityResults[cve] = resultKindToApplicabilityStatus(sarifResult.Kind) - } - return -} - -// Gets a result of one CVE from the scanner, and returns true if the CVE is applicable, false otherwise -func resultKindToApplicabilityStatus(kind *string) utils.ApplicabilityStatus { - if !(kind != nil && *kind == "pass") { - return utils.Applicable +func removeElementFromSlice(skipDirs []string, element string) []string { + deleteIndex := slices.Index(skipDirs, element) + if deleteIndex == -1 { + return skipDirs } - return utils.NotApplicable -} - -func getCveFromRuleId(sarifRuleId string) string { - return strings.TrimPrefix(sarifRuleId, "applic_") + return slices.Delete(skipDirs, deleteIndex, deleteIndex+1) } diff --git a/xray/commands/audit/jas/applicability/applicabilitymanager_test.go b/xray/commands/audit/jas/applicability/applicabilitymanager_test.go index 76a0e567c..e4a232c44 100644 --- a/xray/commands/audit/jas/applicability/applicabilitymanager_test.go +++ b/xray/commands/audit/jas/applicability/applicabilitymanager_test.go @@ -3,7 +3,6 @@ package applicability import ( "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" "github.com/jfrog/jfrog-cli-core/v2/xray/commands/audit/jas" - "github.com/jfrog/jfrog-cli-core/v2/xray/utils" "github.com/jfrog/jfrog-client-go/xray/services" "github.com/stretchr/testify/assert" "os" @@ -18,7 +17,7 @@ func TestNewApplicabilityScanManager_InputIsValid(t *testing.T) { scanner, cleanUp := jas.InitJasTest(t) defer cleanUp() // Act - applicabilityManager := newApplicabilityScanManager(jas.FakeBasicXrayResults, mockDirectDependencies, scanner) + applicabilityManager := newApplicabilityScanManager(jas.FakeBasicXrayResults, mockDirectDependencies, scanner, false) // Assert if assert.NotNil(t, applicabilityManager) { @@ -32,7 +31,7 @@ func TestNewApplicabilityScanManager_DependencyTreeDoesntExist(t *testing.T) { scanner, cleanUp := jas.InitJasTest(t) defer cleanUp() // Act - applicabilityManager := newApplicabilityScanManager(jas.FakeBasicXrayResults, nil, scanner) + applicabilityManager := newApplicabilityScanManager(jas.FakeBasicXrayResults, nil, scanner, false) // Assert if assert.NotNil(t, applicabilityManager) { @@ -69,9 +68,14 @@ func TestNewApplicabilityScanManager_NoDirectDependenciesInScan(t *testing.T) { // Act scanner, cleanUp := jas.InitJasTest(t) defer cleanUp() - applicabilityManager := newApplicabilityScanManager(noDirectDependenciesResults, mockDirectDependencies, scanner) + applicabilityManager := newApplicabilityScanManager(noDirectDependenciesResults, mockDirectDependencies, scanner, false) + assertApplicabilityScanner(t, applicabilityManager) + // ThirdPartyContextual shouldn't change anything here as this is not npm. + applicabilityManager = newApplicabilityScanManager(noDirectDependenciesResults, mockDirectDependencies, scanner, true) + assertApplicabilityScanner(t, applicabilityManager) +} - // Assert +func assertApplicabilityScanner(t *testing.T, applicabilityManager *ApplicabilityScanManager) { if assert.NotNil(t, applicabilityManager) { assert.NotEmpty(t, applicabilityManager.scanner.ConfigFileName) assert.NotEmpty(t, applicabilityManager.scanner.ResultsFileName) @@ -85,7 +89,7 @@ func TestNewApplicabilityScanManager_MultipleDependencyTrees(t *testing.T) { scanner, cleanUp := jas.InitJasTest(t) defer cleanUp() // Act - applicabilityManager := newApplicabilityScanManager(jas.FakeBasicXrayResults, mockMultiRootDirectDependencies, scanner) + applicabilityManager := newApplicabilityScanManager(jas.FakeBasicXrayResults, mockMultiRootDirectDependencies, scanner, false) // Assert if assert.NotNil(t, applicabilityManager) { @@ -111,7 +115,7 @@ func TestNewApplicabilityScanManager_ViolationsDontExistInResults(t *testing.T) defer cleanUp() // Act - applicabilityManager := newApplicabilityScanManager(noViolationScanResponse, mockDirectDependencies, scanner) + applicabilityManager := newApplicabilityScanManager(noViolationScanResponse, mockDirectDependencies, scanner, false) // Assert if assert.NotNil(t, applicabilityManager) { @@ -137,7 +141,7 @@ func TestNewApplicabilityScanManager_VulnerabilitiesDontExist(t *testing.T) { defer cleanUp() // Act - applicabilityManager := newApplicabilityScanManager(noVulnerabilitiesScanResponse, mockDirectDependencies, scanner) + applicabilityManager := newApplicabilityScanManager(noVulnerabilitiesScanResponse, mockDirectDependencies, scanner, false) // Assert if assert.NotNil(t, applicabilityManager) { @@ -151,8 +155,7 @@ func TestApplicabilityScanManager_ShouldRun_TechnologiesNotEligibleForScan(t *te scanner, cleanUp := jas.InitJasTest(t) defer cleanUp() - results, err := RunApplicabilityScan(jas.FakeBasicXrayResults, mockDirectDependencies, - []coreutils.Technology{coreutils.Nuget, coreutils.Go}, scanner) + results, err := RunApplicabilityScan(jas.FakeBasicXrayResults, mockDirectDependencies, []coreutils.Technology{coreutils.Nuget, coreutils.Go}, scanner, false) // Assert assert.Nil(t, results) @@ -164,7 +167,7 @@ func TestApplicabilityScanManager_ShouldRun_ScanResultsAreEmpty(t *testing.T) { scanner, cleanUp := jas.InitJasTest(t) defer cleanUp() - applicabilityManager := newApplicabilityScanManager(nil, mockDirectDependencies, scanner) + applicabilityManager := newApplicabilityScanManager(nil, mockDirectDependencies, scanner, false) // Assert eligible := applicabilityManager.shouldRunApplicabilityScan([]coreutils.Technology{coreutils.Npm}) @@ -248,7 +251,7 @@ func TestCreateConfigFile_VerifyFileWasCreated(t *testing.T) { scanner, cleanUp := jas.InitJasTest(t) defer cleanUp() - applicabilityManager := newApplicabilityScanManager(jas.FakeBasicXrayResults, []string{"issueId_1_direct_dependency", "issueId_2_direct_dependency"}, scanner) + applicabilityManager := newApplicabilityScanManager(jas.FakeBasicXrayResults, []string{"issueId_1_direct_dependency", "issueId_2_direct_dependency"}, scanner, false) currWd, err := coreutils.GetWorkingDirectory() assert.NoError(t, err) @@ -272,17 +275,16 @@ func TestParseResults_EmptyResults_AllCvesShouldGetUnknown(t *testing.T) { scanner, cleanUp := jas.InitJasTest(t) defer cleanUp() - applicabilityManager := newApplicabilityScanManager(jas.FakeBasicXrayResults, mockDirectDependencies, scanner) + applicabilityManager := newApplicabilityScanManager(jas.FakeBasicXrayResults, mockDirectDependencies, scanner, false) applicabilityManager.scanner.ResultsFileName = filepath.Join(jas.GetTestDataPath(), "applicability-scan", "empty-results.sarif") // Act - results, err := applicabilityManager.getScanResults() + var err error + applicabilityManager.applicabilityScanResults, err = jas.ReadJasScanRunsFromFile(applicabilityManager.scanner.ResultsFileName, scanner.WorkingDirs[0]) - // Assert - assert.NoError(t, err) - assert.Equal(t, 5, len(results)) - for _, cveResult := range results { - assert.Equal(t, utils.ApplicabilityUndetermined, cveResult) + if assert.NoError(t, err) { + assert.Len(t, applicabilityManager.applicabilityScanResults, 1) + assert.Empty(t, applicabilityManager.applicabilityScanResults[0].Results) } } @@ -290,33 +292,32 @@ func TestParseResults_ApplicableCveExist(t *testing.T) { // Arrange scanner, cleanUp := jas.InitJasTest(t) defer cleanUp() - applicabilityManager := newApplicabilityScanManager(jas.FakeBasicXrayResults, mockDirectDependencies, scanner) + applicabilityManager := newApplicabilityScanManager(jas.FakeBasicXrayResults, mockDirectDependencies, scanner, false) applicabilityManager.scanner.ResultsFileName = filepath.Join(jas.GetTestDataPath(), "applicability-scan", "applicable-cve-results.sarif") // Act - results, err := applicabilityManager.getScanResults() + var err error + applicabilityManager.applicabilityScanResults, err = jas.ReadJasScanRunsFromFile(applicabilityManager.scanner.ResultsFileName, scanner.WorkingDirs[0]) - // Assert - assert.NoError(t, err) - assert.Equal(t, 5, len(results)) - assert.Equal(t, utils.Applicable, results["testCve1"]) - assert.Equal(t, utils.NotApplicable, results["testCve3"]) + if assert.NoError(t, err) && assert.NotNil(t, applicabilityManager.applicabilityScanResults) { + assert.Len(t, applicabilityManager.applicabilityScanResults, 1) + assert.NotEmpty(t, applicabilityManager.applicabilityScanResults[0].Results) + } } func TestParseResults_AllCvesNotApplicable(t *testing.T) { // Arrange scanner, cleanUp := jas.InitJasTest(t) defer cleanUp() - applicabilityManager := newApplicabilityScanManager(jas.FakeBasicXrayResults, mockDirectDependencies, scanner) + applicabilityManager := newApplicabilityScanManager(jas.FakeBasicXrayResults, mockDirectDependencies, scanner, false) applicabilityManager.scanner.ResultsFileName = filepath.Join(jas.GetTestDataPath(), "applicability-scan", "no-applicable-cves-results.sarif") // Act - results, err := applicabilityManager.getScanResults() + var err error + applicabilityManager.applicabilityScanResults, err = jas.ReadJasScanRunsFromFile(applicabilityManager.scanner.ResultsFileName, scanner.WorkingDirs[0]) - // Assert - assert.NoError(t, err) - assert.Equal(t, 5, len(results)) - for _, cveResult := range results { - assert.Equal(t, utils.NotApplicable, cveResult) + if assert.NoError(t, err) && assert.NotNil(t, applicabilityManager.applicabilityScanResults) { + assert.Len(t, applicabilityManager.applicabilityScanResults, 1) + assert.NotEmpty(t, applicabilityManager.applicabilityScanResults[0].Results) } } diff --git a/xray/commands/audit/jas/common.go b/xray/commands/audit/jas/common.go index 30fac5bc7..1f4082bfe 100644 --- a/xray/commands/audit/jas/common.go +++ b/xray/commands/audit/jas/common.go @@ -2,6 +2,11 @@ package jas import ( "errors" + "os" + "path/filepath" + "strings" + "testing" + rtutils "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" @@ -12,14 +17,23 @@ import ( "github.com/owenrumney/go-sarif/v2/sarif" "github.com/stretchr/testify/assert" "gopkg.in/yaml.v3" - "os" - "path/filepath" - "strings" - "testing" +) + +const ( + NodeModulesPattern = "**/*node_modules*/**" ) var ( - SkippedDirs = []string{"**/*test*/**", "**/*venv*/**", "**/*node_modules*/**", "**/*target*/**"} + SkippedDirs = []string{"**/*test*/**", "**/*venv*/**", NodeModulesPattern, "**/*target*/**"} + + mapSeverityToScore = map[string]string{ + "": "0.0", + "unknown": "0.0", + "low": "3.9", + "medium": "6.9", + "high": "8.9", + "critical": "10", + } ) type JasScanner struct { @@ -31,7 +45,7 @@ type JasScanner struct { ScannerDirCleanupFunc func() error } -func NewJasScanner(workingDirs []string, serverDetails *config.ServerDetails) (scanner *JasScanner, err error) { +func NewJasScanner(workingDirs []string, serverDetails *config.ServerDetails, multiScanId string) (scanner *JasScanner, err error) { scanner = &JasScanner{} if scanner.AnalyzerManager.AnalyzerManagerFullPath, err = utils.GetAnalyzerManagerExecutable(); err != nil { return @@ -47,6 +61,7 @@ func NewJasScanner(workingDirs []string, serverDetails *config.ServerDetails) (s scanner.ConfigFileName = filepath.Join(tempDir, "config.yaml") scanner.ResultsFileName = filepath.Join(tempDir, "results.sarif") scanner.WorkingDirs, err = coreutils.GetFullPathsWorkingDirs(workingDirs) + scanner.AnalyzerManager.MultiScanId = multiScanId return } @@ -88,46 +103,54 @@ func deleteJasProcessFiles(configFile string, resultFile string) error { return errorutils.CheckError(err) } -func GetSourceCodeScanResults(resultsFileName, workingDir string, scanType utils.JasScanType) (results []utils.SourceCodeScanResult, err error) { - // Read Sarif format results generated from the Jas scanner - report, err := sarif.Open(resultsFileName) - if errorutils.CheckError(err) != nil { - return nil, err - } - var sarifResults []*sarif.Result - if len(report.Runs) > 0 { - // Jas scanners returns results in a single run entry - sarifResults = report.Runs[0].Results +func ReadJasScanRunsFromFile(fileName, wd string) (sarifRuns []*sarif.Run, err error) { + if sarifRuns, err = utils.ReadScanRunsFromFile(fileName); err != nil { + return } - resultPointers := convertSarifResultsToSourceCodeScanResults(sarifResults, workingDir, scanType) - for _, res := range resultPointers { - results = append(results, *res) + for _, sarifRun := range sarifRuns { + // Jas reports has only one invocation + // Set the actual working directory to the invocation, not the analyzerManager directory + // Also used to calculate relative paths if needed with it + sarifRun.Invocations[0].WorkingDirectory.WithUri(wd) + // Process runs values + sarifRun.Results = excludeSuppressResults(sarifRun.Results) + addScoreToRunRules(sarifRun) } - return results, nil + return } -func convertSarifResultsToSourceCodeScanResults(sarifResults []*sarif.Result, workingDir string, scanType utils.JasScanType) []*utils.SourceCodeScanResult { - var sourceCodeScanResults []*utils.SourceCodeScanResult +func excludeSuppressResults(sarifResults []*sarif.Result) []*sarif.Result { + results := []*sarif.Result{} for _, sarifResult := range sarifResults { - // Describes a request to “suppress” a result (to exclude it from result lists) if len(sarifResult.Suppressions) > 0 { + // Describes a request to “suppress” a result (to exclude it from result lists) continue } - // Convert - currentResult := utils.GetResultIfExists(sarifResult, workingDir, sourceCodeScanResults) - if currentResult == nil { - currentResult = utils.ConvertSarifResultToSourceCodeScanResult(sarifResult, workingDir) - // Set specific Jas scan attributes - if scanType == utils.Secrets { - currentResult.Text = hideSecret(utils.GetResultLocationSnippet(sarifResult.Locations[0])) + results = append(results, sarifResult) + } + return results +} + +func addScoreToRunRules(sarifRun *sarif.Run) { + for _, sarifResult := range sarifRun.Results { + if rule, err := sarifRun.GetRuleById(*sarifResult.RuleID); err == nil { + // Add to the rule security-severity score based on results severity + score := convertToScore(utils.GetResultSeverity(sarifResult)) + if score != utils.MissingCveScore { + if rule.Properties == nil { + rule.WithProperties(sarif.NewPropertyBag().Properties) + } + rule.Properties["security-severity"] = score } - sourceCodeScanResults = append(sourceCodeScanResults, currentResult) - } - if scanType == utils.Sast { - currentResult.CodeFlow = append(currentResult.CodeFlow, utils.GetResultCodeFlows(sarifResult, workingDir)...) } } - return sourceCodeScanResults +} + +func convertToScore(severity string) string { + if level, ok := mapSeverityToScore[strings.ToLower(severity)]; ok { + return level + } + return "" } func CreateScannersConfigFile(fileName string, fileContent interface{}) error { @@ -139,13 +162,6 @@ func CreateScannersConfigFile(fileName string, fileContent interface{}) error { return errorutils.CheckError(err) } -func hideSecret(secret string) string { - if len(secret) <= 3 { - return "***" - } - return secret[:3] + strings.Repeat("*", 12) -} - var FakeServerDetails = config.ServerDetails{ Url: "platformUrl", Password: "password", @@ -170,7 +186,7 @@ var FakeBasicXrayResults = []services.ScanResponse{ func InitJasTest(t *testing.T, workingDirs ...string) (*JasScanner, func()) { assert.NoError(t, rtutils.DownloadAnalyzerManagerIfNeeded()) - scanner, err := NewJasScanner(workingDirs, &FakeServerDetails) + scanner, err := NewJasScanner(workingDirs, &FakeServerDetails, "") assert.NoError(t, err) return scanner, func() { assert.NoError(t, scanner.ScannerDirCleanupFunc()) diff --git a/xray/commands/audit/jas/common_test.go b/xray/commands/audit/jas/common_test.go deleted file mode 100644 index 305629f31..000000000 --- a/xray/commands/audit/jas/common_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package jas - -import ( - "github.com/stretchr/testify/assert" - "testing" -) - -func TestHideSecret(t *testing.T) { - tests := []struct { - secret string - expectedOutput string - }{ - {secret: "", expectedOutput: "***"}, - {secret: "12", expectedOutput: "***"}, - {secret: "123", expectedOutput: "***"}, - {secret: "123456789", expectedOutput: "123************"}, - {secret: "3478hfnkjhvd848446gghgfh", expectedOutput: "347************"}, - } - - for _, test := range tests { - assert.Equal(t, test.expectedOutput, hideSecret(test.secret)) - } -} diff --git a/xray/commands/audit/jas/iac/iacscanner.go b/xray/commands/audit/jas/iac/iacscanner.go index c4fe18062..c4ccfdd39 100644 --- a/xray/commands/audit/jas/iac/iacscanner.go +++ b/xray/commands/audit/jas/iac/iacscanner.go @@ -6,6 +6,7 @@ import ( "github.com/jfrog/jfrog-cli-core/v2/xray/utils" "github.com/jfrog/jfrog-client-go/utils/log" + "github.com/owenrumney/go-sarif/v2/sarif" ) const ( @@ -14,7 +15,7 @@ const ( ) type IacScanManager struct { - iacScannerResults []utils.SourceCodeScanResult + iacScannerResults []*sarif.Run scanner *jas.JasScanner } @@ -26,7 +27,7 @@ type IacScanManager struct { // []utils.SourceCodeScanResult: a list of the iac violations that were found. // bool: true if the user is entitled to iac scan, false otherwise. // error: An error object (if any). -func RunIacScan(scanner *jas.JasScanner) (results []utils.SourceCodeScanResult, err error) { +func RunIacScan(scanner *jas.JasScanner) (results []*sarif.Run, err error) { iacScanManager := newIacScanManager(scanner) log.Info("Running IaC scanning...") if err = iacScanManager.scanner.Run(iacScanManager); err != nil { @@ -34,7 +35,7 @@ func RunIacScan(scanner *jas.JasScanner) (results []utils.SourceCodeScanResult, return } if len(iacScanManager.iacScannerResults) > 0 { - log.Info("Found", len(iacScanManager.iacScannerResults), "IaC vulnerabilities") + log.Info("Found", utils.GetResultsLocationCount(iacScanManager.iacScannerResults...), "IaC vulnerabilities") } results = iacScanManager.iacScannerResults return @@ -42,7 +43,7 @@ func RunIacScan(scanner *jas.JasScanner) (results []utils.SourceCodeScanResult, func newIacScanManager(scanner *jas.JasScanner) (manager *IacScanManager) { return &IacScanManager{ - iacScannerResults: []utils.SourceCodeScanResult{}, + iacScannerResults: []*sarif.Run{}, scanner: scanner, } } @@ -55,8 +56,8 @@ func (iac *IacScanManager) Run(wd string) (err error) { if err = iac.runAnalyzerManager(); err != nil { return } - var workingDirResults []utils.SourceCodeScanResult - if workingDirResults, err = jas.GetSourceCodeScanResults(scanner.ResultsFileName, wd, utils.IaC); err != nil { + workingDirResults, err := jas.ReadJasScanRunsFromFile(scanner.ResultsFileName, wd) + if err != nil { return } iac.iacScannerResults = append(iac.iacScannerResults, workingDirResults...) diff --git a/xray/commands/audit/jas/iac/iacscanner_test.go b/xray/commands/audit/jas/iac/iacscanner_test.go index a43fef61a..a2332421d 100644 --- a/xray/commands/audit/jas/iac/iacscanner_test.go +++ b/xray/commands/audit/jas/iac/iacscanner_test.go @@ -7,7 +7,6 @@ import ( "testing" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" - "github.com/jfrog/jfrog-cli-core/v2/xray/utils" "github.com/stretchr/testify/assert" ) @@ -58,11 +57,11 @@ func TestIacParseResults_EmptyResults(t *testing.T) { // Act var err error - iacScanManager.iacScannerResults, err = jas.GetSourceCodeScanResults(iacScanManager.scanner.ResultsFileName, scanner.WorkingDirs[0], utils.IaC) - - // Assert - assert.NoError(t, err) - assert.Empty(t, iacScanManager.iacScannerResults) + iacScanManager.iacScannerResults, err = jas.ReadJasScanRunsFromFile(iacScanManager.scanner.ResultsFileName, scanner.WorkingDirs[0]) + if assert.NoError(t, err) && assert.NotNil(t, iacScanManager.iacScannerResults) { + assert.Len(t, iacScanManager.iacScannerResults, 1) + assert.Empty(t, iacScanManager.iacScannerResults[0].Results) + } } func TestIacParseResults_ResultsContainIacViolations(t *testing.T) { @@ -74,10 +73,9 @@ func TestIacParseResults_ResultsContainIacViolations(t *testing.T) { // Act var err error - iacScanManager.iacScannerResults, err = jas.GetSourceCodeScanResults(iacScanManager.scanner.ResultsFileName, scanner.WorkingDirs[0], utils.IaC) - - // Assert - assert.NoError(t, err) - assert.NotEmpty(t, iacScanManager.iacScannerResults) - assert.Equal(t, 4, len(iacScanManager.iacScannerResults)) + iacScanManager.iacScannerResults, err = jas.ReadJasScanRunsFromFile(iacScanManager.scanner.ResultsFileName, scanner.WorkingDirs[0]) + if assert.NoError(t, err) && assert.NotNil(t, iacScanManager.iacScannerResults) { + assert.Len(t, iacScanManager.iacScannerResults, 1) + assert.Len(t, iacScanManager.iacScannerResults[0].Results, 4) + } } diff --git a/xray/commands/audit/jas/sast/sastscanner.go b/xray/commands/audit/jas/sast/sastscanner.go index dc31fa9ea..35211396f 100644 --- a/xray/commands/audit/jas/sast/sastscanner.go +++ b/xray/commands/audit/jas/sast/sastscanner.go @@ -4,6 +4,8 @@ import ( "github.com/jfrog/jfrog-cli-core/v2/xray/commands/audit/jas" "github.com/jfrog/jfrog-cli-core/v2/xray/utils" "github.com/jfrog/jfrog-client-go/utils/log" + "github.com/owenrumney/go-sarif/v2/sarif" + "golang.org/x/exp/maps" ) const ( @@ -11,11 +13,11 @@ const ( ) type SastScanManager struct { - sastScannerResults []utils.SourceCodeScanResult + sastScannerResults []*sarif.Run scanner *jas.JasScanner } -func RunSastScan(scanner *jas.JasScanner) (results []utils.SourceCodeScanResult, err error) { +func RunSastScan(scanner *jas.JasScanner) (results []*sarif.Run, err error) { sastScanManager := newSastScanManager(scanner) log.Info("Running SAST scanning...") if err = sastScanManager.scanner.Run(sastScanManager); err != nil { @@ -23,7 +25,7 @@ func RunSastScan(scanner *jas.JasScanner) (results []utils.SourceCodeScanResult, return } if len(sastScanManager.sastScannerResults) > 0 { - log.Info("Found", len(sastScanManager.sastScannerResults), "SAST vulnerabilities") + log.Info("Found", utils.GetResultsLocationCount(sastScanManager.sastScannerResults...), "SAST vulnerabilities") } results = sastScanManager.sastScannerResults return @@ -31,7 +33,7 @@ func RunSastScan(scanner *jas.JasScanner) (results []utils.SourceCodeScanResult, func newSastScanManager(scanner *jas.JasScanner) (manager *SastScanManager) { return &SastScanManager{ - sastScannerResults: []utils.SourceCodeScanResult{}, + sastScannerResults: []*sarif.Run{}, scanner: scanner, } } @@ -41,14 +43,53 @@ func (ssm *SastScanManager) Run(wd string) (err error) { if err = ssm.runAnalyzerManager(wd); err != nil { return } - var workingDirResults []utils.SourceCodeScanResult - if workingDirResults, err = jas.GetSourceCodeScanResults(scanner.ResultsFileName, wd, utils.Sast); err != nil { + workingDirRuns, err := jas.ReadJasScanRunsFromFile(scanner.ResultsFileName, wd) + if err != nil { return } - ssm.sastScannerResults = append(ssm.sastScannerResults, workingDirResults...) + ssm.sastScannerResults = append(ssm.sastScannerResults, groupResultsByLocation(workingDirRuns)...) return } func (ssm *SastScanManager) runAnalyzerManager(wd string) error { return ssm.scanner.AnalyzerManager.Exec(ssm.scanner.ResultsFileName, sastScanCommand, wd, ssm.scanner.ServerDetails) } + +// In the Sast scanner, there can be multiple results with the same location. +// The only difference is that their CodeFlow values are different. +// We combine those under the same result location value +func groupResultsByLocation(sarifRuns []*sarif.Run) []*sarif.Run { + for _, sastRun := range sarifRuns { + locationToResult := map[string]*sarif.Result{} + for _, sastResult := range sastRun.Results { + resultID := getResultId(sastResult) + if result, exists := locationToResult[resultID]; exists { + result.CodeFlows = append(result.CodeFlows, sastResult.CodeFlows...) + } else { + locationToResult[resultID] = sastResult + } + } + sastRun.Results = maps.Values(locationToResult) + } + return sarifRuns +} + +// In Sast there is only one location for each result +func getResultFileName(result *sarif.Result) string { + if len(result.Locations) > 0 { + return utils.GetLocationFileName(result.Locations[0]) + } + return "" +} + +// In Sast there is only one location for each result +func getResultStartLocationInFile(result *sarif.Result) string { + if len(result.Locations) > 0 { + return utils.GetStartLocationInFile(result.Locations[0]) + } + return "" +} + +func getResultId(result *sarif.Result) string { + return getResultFileName(result) + getResultStartLocationInFile(result) + utils.GetResultSeverity(result) + utils.GetResultMsgText(result) +} diff --git a/xray/commands/audit/jas/sast/sastscanner_test.go b/xray/commands/audit/jas/sast/sastscanner_test.go index 969ab80ce..66c423403 100644 --- a/xray/commands/audit/jas/sast/sastscanner_test.go +++ b/xray/commands/audit/jas/sast/sastscanner_test.go @@ -1,11 +1,11 @@ package sast import ( - "github.com/jfrog/jfrog-cli-core/v2/xray/commands/audit/jas" "path/filepath" "testing" - "github.com/jfrog/jfrog-cli-core/v2/xray/utils" + "github.com/jfrog/jfrog-cli-core/v2/xray/commands/audit/jas" + "github.com/stretchr/testify/assert" ) @@ -34,11 +34,16 @@ func TestSastParseResults_EmptyResults(t *testing.T) { // Act var err error - sastScanManager.sastScannerResults, err = jas.GetSourceCodeScanResults(sastScanManager.scanner.ResultsFileName, scanner.WorkingDirs[0], utils.Sast) + sastScanManager.sastScannerResults, err = jas.ReadJasScanRunsFromFile(sastScanManager.scanner.ResultsFileName, scanner.WorkingDirs[0]) // Assert - assert.NoError(t, err) - assert.Empty(t, sastScanManager.sastScannerResults) + if assert.NoError(t, err) && assert.NotNil(t, sastScanManager.sastScannerResults) { + assert.Len(t, sastScanManager.sastScannerResults, 1) + assert.Empty(t, sastScanManager.sastScannerResults[0].Results) + sastScanManager.sastScannerResults = groupResultsByLocation(sastScanManager.sastScannerResults) + assert.Len(t, sastScanManager.sastScannerResults, 1) + assert.Empty(t, sastScanManager.sastScannerResults[0].Results) + } } func TestSastParseResults_ResultsContainIacViolations(t *testing.T) { @@ -50,11 +55,14 @@ func TestSastParseResults_ResultsContainIacViolations(t *testing.T) { // Act var err error - sastScanManager.sastScannerResults, err = jas.GetSourceCodeScanResults(sastScanManager.scanner.ResultsFileName, scanner.WorkingDirs[0], utils.Sast) + sastScanManager.sastScannerResults, err = jas.ReadJasScanRunsFromFile(sastScanManager.scanner.ResultsFileName, scanner.WorkingDirs[0]) // Assert - assert.NoError(t, err) - assert.NotEmpty(t, sastScanManager.sastScannerResults) - // File has 4 results, 2 of them at the same location different codeFlow - assert.Equal(t, 3, len(sastScanManager.sastScannerResults)) + if assert.NoError(t, err) && assert.NotNil(t, sastScanManager.sastScannerResults) { + assert.Len(t, sastScanManager.sastScannerResults, 1) + assert.NotEmpty(t, sastScanManager.sastScannerResults[0].Results) + sastScanManager.sastScannerResults = groupResultsByLocation(sastScanManager.sastScannerResults) + // File has 4 results, 2 of them at the same location different codeFlow + assert.Len(t, sastScanManager.sastScannerResults[0].Results, 3) + } } diff --git a/xray/commands/audit/jas/secrets/secretsscanner.go b/xray/commands/audit/jas/secrets/secretsscanner.go index ef6722ec2..cf5df05f8 100644 --- a/xray/commands/audit/jas/secrets/secretsscanner.go +++ b/xray/commands/audit/jas/secrets/secretsscanner.go @@ -1,10 +1,13 @@ package secrets import ( + "path/filepath" + "strings" + "github.com/jfrog/jfrog-cli-core/v2/xray/commands/audit/jas" "github.com/jfrog/jfrog-cli-core/v2/xray/utils" "github.com/jfrog/jfrog-client-go/utils/log" - "path/filepath" + "github.com/owenrumney/go-sarif/v2/sarif" ) const ( @@ -13,7 +16,7 @@ const ( ) type SecretScanManager struct { - secretsScannerResults []utils.SourceCodeScanResult + secretsScannerResults []*sarif.Run scanner *jas.JasScanner } @@ -24,7 +27,7 @@ type SecretScanManager struct { // Return values: // []utils.IacOrSecretResult: a list of the secrets that were found. // error: An error object (if any). -func RunSecretsScan(scanner *jas.JasScanner) (results []utils.SourceCodeScanResult, err error) { +func RunSecretsScan(scanner *jas.JasScanner) (results []*sarif.Run, err error) { secretScanManager := newSecretsScanManager(scanner) log.Info("Running secrets scanning...") if err = secretScanManager.scanner.Run(secretScanManager); err != nil { @@ -33,14 +36,14 @@ func RunSecretsScan(scanner *jas.JasScanner) (results []utils.SourceCodeScanResu } results = secretScanManager.secretsScannerResults if len(results) > 0 { - log.Info("Found", len(results), "secrets") + log.Info("Found", utils.GetResultsLocationCount(results...), "secrets") } return } func newSecretsScanManager(scanner *jas.JasScanner) (manager *SecretScanManager) { return &SecretScanManager{ - secretsScannerResults: []utils.SourceCodeScanResult{}, + secretsScannerResults: []*sarif.Run{}, scanner: scanner, } } @@ -53,11 +56,11 @@ func (s *SecretScanManager) Run(wd string) (err error) { if err = s.runAnalyzerManager(); err != nil { return } - var workingDirResults []utils.SourceCodeScanResult - if workingDirResults, err = jas.GetSourceCodeScanResults(scanner.ResultsFileName, wd, utils.Secrets); err != nil { + workingDirRuns, err := jas.ReadJasScanRunsFromFile(scanner.ResultsFileName, wd) + if err != nil { return } - s.secretsScannerResults = append(s.secretsScannerResults, workingDirResults...) + s.secretsScannerResults = append(s.secretsScannerResults, processSecretScanRuns(workingDirRuns)...) return } @@ -89,3 +92,23 @@ func (s *SecretScanManager) createConfigFile(currentWd string) error { func (s *SecretScanManager) runAnalyzerManager() error { return s.scanner.AnalyzerManager.Exec(s.scanner.ConfigFileName, secretsScanCommand, filepath.Dir(s.scanner.AnalyzerManager.AnalyzerManagerFullPath), s.scanner.ServerDetails) } + +func maskSecret(secret string) string { + if len(secret) <= 3 { + return "***" + } + return secret[:3] + strings.Repeat("*", 12) +} + +func processSecretScanRuns(sarifRuns []*sarif.Run) []*sarif.Run { + for _, secretRun := range sarifRuns { + // Hide discovered secrets value + for _, secretResult := range secretRun.Results { + for _, location := range secretResult.Locations { + secret := utils.GetLocationSnippetPointer(location) + utils.SetLocationSnippet(location, maskSecret(*secret)) + } + } + } + return sarifRuns +} diff --git a/xray/commands/audit/jas/secrets/secretsscanner_test.go b/xray/commands/audit/jas/secrets/secretsscanner_test.go index f403adc86..14e917e16 100644 --- a/xray/commands/audit/jas/secrets/secretsscanner_test.go +++ b/xray/commands/audit/jas/secrets/secretsscanner_test.go @@ -1,13 +1,13 @@ package secrets import ( - "github.com/jfrog/jfrog-cli-core/v2/xray/commands/audit/jas" "os" "path/filepath" "testing" + "github.com/jfrog/jfrog-cli-core/v2/xray/commands/audit/jas" + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" - "github.com/jfrog/jfrog-cli-core/v2/xray/utils" "github.com/stretchr/testify/assert" ) @@ -65,11 +65,17 @@ func TestParseResults_EmptyResults(t *testing.T) { // Act var err error - secretScanManager.secretsScannerResults, err = jas.GetSourceCodeScanResults(secretScanManager.scanner.ResultsFileName, scanner.WorkingDirs[0], utils.Secrets) + secretScanManager.secretsScannerResults, err = jas.ReadJasScanRunsFromFile(secretScanManager.scanner.ResultsFileName, scanner.WorkingDirs[0]) // Assert - assert.NoError(t, err) - assert.Empty(t, secretScanManager.secretsScannerResults) + if assert.NoError(t, err) && assert.NotNil(t, secretScanManager.secretsScannerResults) { + assert.Len(t, secretScanManager.secretsScannerResults, 1) + assert.Empty(t, secretScanManager.secretsScannerResults[0].Results) + secretScanManager.secretsScannerResults = processSecretScanRuns(secretScanManager.secretsScannerResults) + assert.Len(t, secretScanManager.secretsScannerResults, 1) + assert.Empty(t, secretScanManager.secretsScannerResults[0].Results) + } + } func TestParseResults_ResultsContainSecrets(t *testing.T) { @@ -82,12 +88,18 @@ func TestParseResults_ResultsContainSecrets(t *testing.T) { // Act var err error - secretScanManager.secretsScannerResults, err = jas.GetSourceCodeScanResults(secretScanManager.scanner.ResultsFileName, scanner.WorkingDirs[0], utils.Secrets) + secretScanManager.secretsScannerResults, err = jas.ReadJasScanRunsFromFile(secretScanManager.scanner.ResultsFileName, scanner.WorkingDirs[0]) // Assert + if assert.NoError(t, err) && assert.NotNil(t, secretScanManager.secretsScannerResults) { + assert.Len(t, secretScanManager.secretsScannerResults, 1) + assert.NotEmpty(t, secretScanManager.secretsScannerResults[0].Results) + secretScanManager.secretsScannerResults = processSecretScanRuns(secretScanManager.secretsScannerResults) + assert.Len(t, secretScanManager.secretsScannerResults, 1) + assert.Len(t, secretScanManager.secretsScannerResults[0].Results, 7) + } assert.NoError(t, err) - assert.NotEmpty(t, secretScanManager.secretsScannerResults) - assert.Equal(t, 7, len(secretScanManager.secretsScannerResults)) + } func TestGetSecretsScanResults_AnalyzerManagerReturnsError(t *testing.T) { @@ -100,3 +112,20 @@ func TestGetSecretsScanResults_AnalyzerManagerReturnsError(t *testing.T) { assert.ErrorContains(t, err, "failed to run Secrets scan") assert.Nil(t, secretsResults) } + +func TestHideSecret(t *testing.T) { + tests := []struct { + secret string + expectedOutput string + }{ + {secret: "", expectedOutput: "***"}, + {secret: "12", expectedOutput: "***"}, + {secret: "123", expectedOutput: "***"}, + {secret: "123456789", expectedOutput: "123************"}, + {secret: "3478hfnkjhvd848446gghgfh", expectedOutput: "347************"}, + } + + for _, test := range tests { + assert.Equal(t, test.expectedOutput, maskSecret(test.secret)) + } +} diff --git a/xray/commands/audit/jasrunner.go b/xray/commands/audit/jasrunner.go index 9fefa7cb6..76a59ff8e 100644 --- a/xray/commands/audit/jasrunner.go +++ b/xray/commands/audit/jasrunner.go @@ -14,12 +14,12 @@ import ( ) func runJasScannersAndSetResults(scanResults *utils.ExtendedScanResults, directDependencies []string, - serverDetails *config.ServerDetails, workingDirs []string, progress io.ProgressMgr) (err error) { + serverDetails *config.ServerDetails, workingDirs []string, progress io.ProgressMgr, multiScanId string, thirdPartyApplicabilityScan bool) (err error) { if serverDetails == nil || len(serverDetails.Url) == 0 { log.Warn("To include 'Advanced Security' scan as part of the audit output, please run the 'jf c add' command before running this command.") return } - scanner, err := jas.NewJasScanner(workingDirs, serverDetails) + scanner, err := jas.NewJasScanner(workingDirs, serverDetails, multiScanId) if err != nil { return } @@ -30,10 +30,14 @@ func runJasScannersAndSetResults(scanResults *utils.ExtendedScanResults, directD if progress != nil { progress.SetHeadlineMsg("Running applicability scanning") } - scanResults.ApplicabilityScanResults, err = applicability.RunApplicabilityScan(scanResults.XrayResults, directDependencies, scanResults.ScannedTechnologies, scanner) + scanResults.ApplicabilityScanResults, err = applicability.RunApplicabilityScan(scanResults.XrayResults, directDependencies, scanResults.ScannedTechnologies, scanner, thirdPartyApplicabilityScan) if err != nil { return } + // Don't execute other scanners when scanning third party dependencies. + if thirdPartyApplicabilityScan { + return + } if progress != nil { progress.SetHeadlineMsg("Running secrets scanning") } @@ -54,6 +58,6 @@ func runJasScannersAndSetResults(scanResults *utils.ExtendedScanResults, directD if progress != nil { progress.SetHeadlineMsg("Running SAST scanning") } - scanResults.SastResults, err = sast.RunSastScan(scanner) + scanResults.SastScanResults, err = sast.RunSastScan(scanner) return } diff --git a/xray/commands/audit/jasrunner_test.go b/xray/commands/audit/jasrunner_test.go index 1fcd60976..0d92f1b18 100644 --- a/xray/commands/audit/jasrunner_test.go +++ b/xray/commands/audit/jasrunner_test.go @@ -22,14 +22,14 @@ func TestGetExtendedScanResults_AnalyzerManagerDoesntExist(t *testing.T) { assert.NoError(t, os.Unsetenv(coreutils.HomeDir)) }() scanResults := &utils.ExtendedScanResults{XrayResults: jas.FakeBasicXrayResults, ScannedTechnologies: []coreutils.Technology{coreutils.Yarn}} - err = runJasScannersAndSetResults(scanResults, []string{"issueId_1_direct_dependency", "issueId_2_direct_dependency"}, &jas.FakeServerDetails, nil, nil) + err = runJasScannersAndSetResults(scanResults, []string{"issueId_1_direct_dependency", "issueId_2_direct_dependency"}, &jas.FakeServerDetails, nil, nil, "", false) // Expect error: assert.Error(t, err) } func TestGetExtendedScanResults_ServerNotValid(t *testing.T) { scanResults := &utils.ExtendedScanResults{XrayResults: jas.FakeBasicXrayResults, ScannedTechnologies: []coreutils.Technology{coreutils.Pip}} - err := runJasScannersAndSetResults(scanResults, []string{"issueId_1_direct_dependency", "issueId_2_direct_dependency"}, nil, nil, nil) + err := runJasScannersAndSetResults(scanResults, []string{"issueId_1_direct_dependency", "issueId_2_direct_dependency"}, nil, nil, nil, "", false) assert.NoError(t, err) } @@ -37,7 +37,7 @@ func TestGetExtendedScanResults_AnalyzerManagerReturnsError(t *testing.T) { mockDirectDependencies := []string{"issueId_2_direct_dependency", "issueId_1_direct_dependency"} assert.NoError(t, rtutils.DownloadAnalyzerManagerIfNeeded()) scanResults := &utils.ExtendedScanResults{XrayResults: jas.FakeBasicXrayResults, ScannedTechnologies: []coreutils.Technology{coreutils.Yarn}} - err := runJasScannersAndSetResults(scanResults, mockDirectDependencies, &jas.FakeServerDetails, nil, nil) + err := runJasScannersAndSetResults(scanResults, mockDirectDependencies, &jas.FakeServerDetails, nil, nil, "", false) // Expect error: assert.ErrorContains(t, err, "failed to run Applicability scan") diff --git a/xray/commands/audit/sca/common.go b/xray/commands/audit/sca/common.go index cb4513978..094746024 100644 --- a/xray/commands/audit/sca/common.go +++ b/xray/commands/audit/sca/common.go @@ -52,6 +52,10 @@ func populateXrayDependencyTree(currNode *xrayUtils.GraphNode, treeHelper map[st func RunXrayDependenciesTreeScanGraph(dependencyTree *xrayUtils.GraphNode, progress ioUtils.ProgressMgr, technology coreutils.Technology, scanGraphParams *scangraph.ScanGraphParams) (results []services.ScanResponse, err error) { scanGraphParams.XrayGraphScanParams().DependenciesGraph = dependencyTree + xscGitInfoContext := scanGraphParams.XrayGraphScanParams().XscGitInfoContext + if xscGitInfoContext != nil { + xscGitInfoContext.Technologies = []string{technology.ToString()} + } scanMessage := fmt.Sprintf("Scanning %d %s dependencies", len(dependencyTree.Nodes), technology) if progress != nil { progress.SetHeadlineMsg(scanMessage) diff --git a/xray/commands/audit/sca/go/golang.go b/xray/commands/audit/sca/go/golang.go index 86c35fc12..2135bcb75 100644 --- a/xray/commands/audit/sca/go/golang.go +++ b/xray/commands/audit/sca/go/golang.go @@ -29,7 +29,7 @@ func BuildDependencyTree(server *config.ServerDetails, remoteGoRepo string) (dep } // Calculate go dependencies graph dependenciesGraph, err := goutils.GetDependenciesGraph(currentDir) - if err != nil { + if err != nil || len(dependenciesGraph) == 0 { return } // Calculate go dependencies list diff --git a/xray/commands/audit/sca/npm/npm.go b/xray/commands/audit/sca/npm/npm.go index 7a646e349..269c6993e 100644 --- a/xray/commands/audit/sca/npm/npm.go +++ b/xray/commands/audit/sca/npm/npm.go @@ -5,14 +5,14 @@ import ( buildinfo "github.com/jfrog/build-info-go/entities" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" "github.com/jfrog/jfrog-cli-core/v2/xray/commands/audit/sca" + "github.com/jfrog/jfrog-cli-core/v2/xray/utils" "github.com/jfrog/jfrog-client-go/utils/log" xrayUtils "github.com/jfrog/jfrog-client-go/xray/services/utils" "golang.org/x/exp/slices" ) const ( - npmPackageTypeIdentifier = "npm://" - ignoreScriptsFlag = "--ignore-scripts" + ignoreScriptsFlag = "--ignore-scripts" ) func BuildDependencyTree(npmArgs []string) (dependencyTrees []*xrayUtils.GraphNode, uniqueDeps []string, err error) { @@ -58,9 +58,9 @@ func addIgnoreScriptsFlag(npmArgs []string) []string { func parseNpmDependenciesList(dependencies []buildinfo.Dependency, packageInfo *biutils.PackageInfo) (*xrayUtils.GraphNode, []string) { treeMap := make(map[string][]string) for _, dependency := range dependencies { - dependencyId := npmPackageTypeIdentifier + dependency.Id + dependencyId := utils.NpmPackageTypeIdentifier + dependency.Id for _, requestedByNode := range dependency.RequestedBy { - parent := npmPackageTypeIdentifier + requestedByNode[0] + parent := utils.NpmPackageTypeIdentifier + requestedByNode[0] if children, ok := treeMap[parent]; ok { treeMap[parent] = appendUniqueChild(children, dependencyId) } else { @@ -68,7 +68,7 @@ func parseNpmDependenciesList(dependencies []buildinfo.Dependency, packageInfo * } } } - return sca.BuildXrayDependencyTree(treeMap, npmPackageTypeIdentifier+packageInfo.BuildInfoModuleId()) + return sca.BuildXrayDependencyTree(treeMap, utils.NpmPackageTypeIdentifier+packageInfo.BuildInfoModuleId()) } func appendUniqueChild(children []string, candidateDependency string) []string { diff --git a/xray/commands/audit/sca/npm/npm_test.go b/xray/commands/audit/sca/npm/npm_test.go index aaa9ea47c..ff4d0aa01 100644 --- a/xray/commands/audit/sca/npm/npm_test.go +++ b/xray/commands/audit/sca/npm/npm_test.go @@ -3,6 +3,7 @@ package npm import ( "encoding/json" "github.com/jfrog/jfrog-cli-core/v2/xray/commands/audit/sca" + "github.com/jfrog/jfrog-cli-core/v2/xray/utils" xrayUtils "github.com/jfrog/jfrog-client-go/xray/services/utils" "os" "testing" @@ -102,7 +103,7 @@ func TestParseNpmDependenciesList(t *testing.T) { } expectedUniqueDeps := []string{xrayDependenciesTree.Id} for _, dep := range dependencies { - expectedUniqueDeps = append(expectedUniqueDeps, npmPackageTypeIdentifier+dep.Id) + expectedUniqueDeps = append(expectedUniqueDeps, utils.NpmPackageTypeIdentifier+dep.Id) } assert.ElementsMatch(t, uniqueDeps, expectedUniqueDeps, "First is actual, Second is Expected") diff --git a/xray/commands/audit/sca/python/python.go b/xray/commands/audit/sca/python/python.go index 4efb193ac..1663ba0c0 100644 --- a/xray/commands/audit/sca/python/python.go +++ b/xray/commands/audit/sca/python/python.go @@ -170,7 +170,6 @@ func installPipDeps(auditPython *AuditPython) (restoreEnv func() error, err erro pipInstallArgs := getPipInstallArgs(auditPython.PipRequirementsFile, remoteUrl) err = executeCommand("python", pipInstallArgs...) if err != nil && auditPython.PipRequirementsFile == "" { - log.Debug(err.Error() + "\nTrying to install using a requirements file...") pipInstallArgs = getPipInstallArgs("requirements.txt", remoteUrl) reqErr := executeCommand("python", pipInstallArgs...) if reqErr != nil { diff --git a/xray/commands/audit/sca/yarn/yarn.go b/xray/commands/audit/sca/yarn/yarn.go index 9df1333c9..ed15121dc 100644 --- a/xray/commands/audit/sca/yarn/yarn.go +++ b/xray/commands/audit/sca/yarn/yarn.go @@ -4,15 +4,12 @@ import ( biUtils "github.com/jfrog/build-info-go/build/utils" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" "github.com/jfrog/jfrog-cli-core/v2/xray/commands/audit/sca" + "github.com/jfrog/jfrog-cli-core/v2/xray/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/log" xrayUtils "github.com/jfrog/jfrog-client-go/xray/services/utils" ) -const ( - npmPackageTypeIdentifier = "npm://" -) - func BuildDependencyTree() (dependencyTrees []*xrayUtils.GraphNode, uniqueDeps []string, err error) { currentDir, err := coreutils.GetWorkingDirectory() if err != nil { @@ -55,5 +52,5 @@ func parseYarnDependenciesMap(dependencies map[string]*biUtils.YarnDependency, r } func getXrayDependencyId(yarnDependency *biUtils.YarnDependency) string { - return npmPackageTypeIdentifier + yarnDependency.Name() + ":" + yarnDependency.Details.Version + return utils.NpmPackageTypeIdentifier + yarnDependency.Name() + ":" + yarnDependency.Details.Version } diff --git a/xray/commands/audit/sca/yarn/yarn_test.go b/xray/commands/audit/sca/yarn/yarn_test.go index bb24f3c0d..aadcdc814 100644 --- a/xray/commands/audit/sca/yarn/yarn_test.go +++ b/xray/commands/audit/sca/yarn/yarn_test.go @@ -1,6 +1,7 @@ package yarn import ( + "github.com/jfrog/jfrog-cli-core/v2/xray/utils" xrayUtils "github.com/jfrog/jfrog-client-go/xray/services/utils" "github.com/stretchr/testify/assert" "testing" @@ -18,30 +19,30 @@ func TestParseYarnDependenciesList(t *testing.T) { "pack5@npm:5.0.0": {Value: "pack5@npm:5.0.0", Details: biutils.YarnDepDetails{Version: "5.0.0", Dependencies: []biutils.YarnDependencyPointer{{Locator: "pack2@npm:2.0.0"}}}}, } - rootXrayId := npmPackageTypeIdentifier + "@jfrog/pack3:3.0.0" + rootXrayId := utils.NpmPackageTypeIdentifier + "@jfrog/pack3:3.0.0" expectedTree := &xrayUtils.GraphNode{ Id: rootXrayId, Nodes: []*xrayUtils.GraphNode{ - {Id: npmPackageTypeIdentifier + "pack1:1.0.0", + {Id: utils.NpmPackageTypeIdentifier + "pack1:1.0.0", Nodes: []*xrayUtils.GraphNode{ - {Id: npmPackageTypeIdentifier + "pack4:4.0.0", + {Id: utils.NpmPackageTypeIdentifier + "pack4:4.0.0", Nodes: []*xrayUtils.GraphNode{}}, }}, - {Id: npmPackageTypeIdentifier + "pack2:2.0.0", + {Id: utils.NpmPackageTypeIdentifier + "pack2:2.0.0", Nodes: []*xrayUtils.GraphNode{ - {Id: npmPackageTypeIdentifier + "pack4:4.0.0", + {Id: utils.NpmPackageTypeIdentifier + "pack4:4.0.0", Nodes: []*xrayUtils.GraphNode{}}, - {Id: npmPackageTypeIdentifier + "pack5:5.0.0", + {Id: utils.NpmPackageTypeIdentifier + "pack5:5.0.0", Nodes: []*xrayUtils.GraphNode{}}, }}, }, } expectedUniqueDeps := []string{ - npmPackageTypeIdentifier + "pack1:1.0.0", - npmPackageTypeIdentifier + "pack2:2.0.0", - npmPackageTypeIdentifier + "pack4:4.0.0", - npmPackageTypeIdentifier + "pack5:5.0.0", - npmPackageTypeIdentifier + "@jfrog/pack3:3.0.0", + utils.NpmPackageTypeIdentifier + "pack1:1.0.0", + utils.NpmPackageTypeIdentifier + "pack2:2.0.0", + utils.NpmPackageTypeIdentifier + "pack4:4.0.0", + utils.NpmPackageTypeIdentifier + "pack5:5.0.0", + utils.NpmPackageTypeIdentifier + "@jfrog/pack3:3.0.0", } xrayDependenciesTree, uniqueDeps := parseYarnDependenciesMap(yarnDependencies, rootXrayId) diff --git a/xray/commands/audit/scarunner.go b/xray/commands/audit/scarunner.go index 3f8e6144e..f78ac8dc2 100644 --- a/xray/commands/audit/scarunner.go +++ b/xray/commands/audit/scarunner.go @@ -79,7 +79,7 @@ func runScaScanOnWorkingDir(params *AuditParams, results *Results, workingDir, r err = errors.Join(err, fmt.Errorf("failed while building '%s' dependency tree:\n%s\n", tech, techErr.Error())) continue } - if len(flattenTree.Nodes) == 0 { + if flattenTree == nil || len(flattenTree.Nodes) == 0 { err = errors.Join(err, errors.New("no dependencies were found. Please try to build your project and re-run the audit command")) continue } @@ -96,15 +96,14 @@ func runScaScanOnWorkingDir(params *AuditParams, results *Results, workingDir, r continue } techResults = sca.BuildImpactPathsForScanResponse(techResults, fullDependencyTrees) - var directDependencies []string - if tech == coreutils.Pip { - // When building pip dependency tree using pipdeptree, some of the direct dependencies are recognized as transitive and missed by the CA scanner. - // Our solution for this case is to send all dependencies to the CA scanner. - directDependencies = getDirectDependenciesFromTree([]*xrayCmdUtils.GraphNode{flattenTree}) + + var dependenciesForApplicabilityScan []string + if shouldUseAllDependencies(params.thirdPartyApplicabilityScan, tech) { + dependenciesForApplicabilityScan = getDirectDependenciesFromTree([]*xrayCmdUtils.GraphNode{flattenTree}) } else { - directDependencies = getDirectDependenciesFromTree(fullDependencyTrees) + dependenciesForApplicabilityScan = getDirectDependenciesFromTree(fullDependencyTrees) } - params.AppendDirectDependencies(directDependencies) + params.AppendDependenciesForApplicabilityScan(dependenciesForApplicabilityScan) results.ExtendedScanResults.XrayResults = append(results.ExtendedScanResults.XrayResults, techResults...) if !results.IsMultipleRootProject { @@ -115,6 +114,14 @@ func runScaScanOnWorkingDir(params *AuditParams, results *Results, workingDir, r return } +// When building pip dependency tree using pipdeptree, some of the direct dependencies are recognized as transitive and missed by the CA scanner. +// Our solution for this case is to send all dependencies to the CA scanner. +// When thirdPartyApplicabilityScan is true, use flatten graph to include all the dependencies in applicability scanning. +// Only npm is supported for this flag. +func shouldUseAllDependencies(thirdPartyApplicabilityScan bool, tech coreutils.Technology) bool { + return tech == coreutils.Pip || (thirdPartyApplicabilityScan && tech == coreutils.Npm) +} + // This function retrieves the dependency trees of the scanned project and extracts a set that contains only the direct dependencies. func getDirectDependenciesFromTree(dependencyTrees []*xrayCmdUtils.GraphNode) []string { directDependencies := datastructures.MakeSet[string]() @@ -158,7 +165,7 @@ func GetTechDependencyTree(params *xrayutils.AuditBasicParams, tech coreutils.Te default: err = errorutils.CheckErrorf("%s is currently not supported", string(tech)) } - if err != nil { + if err != nil || len(uniqueDeps) == 0 { return } log.Debug(fmt.Sprintf("Created '%s' dependency tree with %d nodes. Elapsed time: %.1f seconds.", tech.ToFormal(), len(uniqueDeps), time.Since(startTime).Seconds())) diff --git a/xray/commands/testdata/applicability-scan/applicable-cve-results.sarif b/xray/commands/testdata/applicability-scan/applicable-cve-results.sarif index 71b97e5d6..66aee38a5 100644 --- a/xray/commands/testdata/applicability-scan/applicable-cve-results.sarif +++ b/xray/commands/testdata/applicability-scan/applicable-cve-results.sarif @@ -6,7 +6,7 @@ "name": "JFrog Applicability Scanner", "rules": [ { - "id": "applic_CVE-2021-3807", + "id": "applic_testCve1", "fullDescription": { "text": "The scanner checks whether the vulnerable function `ansi-regex` is called.", "markdown": "The scanner checks whether the vulnerable function `ansi-regex` is called." @@ -17,7 +17,7 @@ } }, { - "id": "applic_CVE-2021-3918", + "id": "applic_testCve3", "fullDescription": { "text": "The scanner checks whether any of the following vulnerable functions are called:\n\n* `json-schema.validate` with external input to its 1st (`instance`) argument.\n* `json-schema.checkPropertyChange` with external input to its 2nd (`schema`) argument.", "markdown": "The scanner checks whether any of the following vulnerable functions are called:\n\n* `json-schema.validate` with external input to its 1st (`instance`) argument.\n* `json-schema.checkPropertyChange` with external input to its 2nd (`schema`) argument." diff --git a/xray/commands/testdata/applicability-scan/no-applicable-cves-results.sarif b/xray/commands/testdata/applicability-scan/no-applicable-cves-results.sarif index a0f9cf39e..4257bc869 100644 --- a/xray/commands/testdata/applicability-scan/no-applicable-cves-results.sarif +++ b/xray/commands/testdata/applicability-scan/no-applicable-cves-results.sarif @@ -6,7 +6,7 @@ "name": "JFrog Applicability Scanner", "rules": [ { - "id": "applic_CVE-2021-3807", + "id": "applic_testCve2", "fullDescription": { "text": "The scanner checks whether the vulnerable function `ansi-regex` is called.", "markdown": "The scanner checks whether the vulnerable function `ansi-regex` is called." @@ -17,7 +17,7 @@ } }, { - "id": "applic_CVE-2021-3918", + "id": "applic_testCve3", "fullDescription": { "text": "The scanner checks whether any of the following vulnerable functions are called:\n\n* `json-schema.validate` with external input to its 1st (`instance`) argument.\n* `json-schema.checkPropertyChange` with external input to its 2nd (`schema`) argument.", "markdown": "The scanner checks whether any of the following vulnerable functions are called:\n\n* `json-schema.validate` with external input to its 1st (`instance`) argument.\n* `json-schema.checkPropertyChange` with external input to its 2nd (`schema`) argument." @@ -26,6 +26,39 @@ "shortDescription": { "text": "Scanner for CVE-2021-3918" } + }, + { + "id": "applic_testCve4", + "fullDescription": { + "text": "The scanner checks whether the vulnerable function `ansi-regex` is called.", + "markdown": "The scanner checks whether the vulnerable function `ansi-regex` is called." + }, + "name": "CVE-2021-3807", + "shortDescription": { + "text": "Scanner for CVE-2021-3807" + } + }, + { + "id": "applic_testCve5", + "fullDescription": { + "text": "The scanner checks whether any of the following vulnerable functions are called:\n\n* `json-schema.validate` with external input to its 1st (`instance`) argument.\n* `json-schema.checkPropertyChange` with external input to its 2nd (`schema`) argument.", + "markdown": "The scanner checks whether any of the following vulnerable functions are called:\n\n* `json-schema.validate` with external input to its 1st (`instance`) argument.\n* `json-schema.checkPropertyChange` with external input to its 2nd (`schema`) argument." + }, + "name": "CVE-2021-3918", + "shortDescription": { + "text": "Scanner for CVE-2021-3918" + } + }, + { + "id": "applic_testCve1", + "fullDescription": { + "text": "The scanner checks whether the vulnerable function `ansi-regex` is called.", + "markdown": "The scanner checks whether the vulnerable function `ansi-regex` is called." + }, + "name": "CVE-2021-3807", + "shortDescription": { + "text": "Scanner for CVE-2021-3807" + } } ], "version": "APPLIC_SCANNERv0.2.3" diff --git a/xray/formats/conversion.go b/xray/formats/conversion.go index 3acb92fbe..83bc09d91 100644 --- a/xray/formats/conversion.go +++ b/xray/formats/conversion.go @@ -1,6 +1,7 @@ package formats import ( + "strconv" "strings" ) @@ -145,32 +146,20 @@ func ConvertToSecretsTableRow(rows []SourceCodeRow) (tableRows []secretsTableRow tableRows = append(tableRows, secretsTableRow{ severity: rows[i].Severity, file: rows[i].File, - lineColumn: rows[i].LineColumn, - text: rows[i].Text, + lineColumn: strconv.Itoa(rows[i].StartLine) + ":" + strconv.Itoa(rows[i].StartColumn), + secret: rows[i].Snippet, }) } return } -func ConvertToIacTableRow(rows []SourceCodeRow) (tableRows []iacTableRow) { +func ConvertToIacOrSastTableRow(rows []SourceCodeRow) (tableRows []iacOrSastTableRow) { for i := range rows { - tableRows = append(tableRows, iacTableRow{ + tableRows = append(tableRows, iacOrSastTableRow{ severity: rows[i].Severity, file: rows[i].File, - lineColumn: rows[i].LineColumn, - text: rows[i].Text, - }) - } - return -} - -func ConvertToSastTableRow(rows []SourceCodeRow) (tableRows []sastTableRow) { - for i := range rows { - tableRows = append(tableRows, sastTableRow{ - severity: rows[i].Severity, - file: rows[i].File, - lineColumn: rows[i].LineColumn, - text: rows[i].Text, + lineColumn: strconv.Itoa(rows[i].StartLine) + ":" + strconv.Itoa(rows[i].StartColumn), + finding: rows[i].Finding, }) } return diff --git a/xray/formats/simplejsonapi.go b/xray/formats/simplejsonapi.go index d56482c25..9bbbdea60 100644 --- a/xray/formats/simplejsonapi.go +++ b/xray/formats/simplejsonapi.go @@ -77,15 +77,19 @@ type OperationalRiskViolationRow struct { type SourceCodeRow struct { Severity string `json:"severity"` SeverityNumValue int `json:"-"` // For sorting - SourceCodeLocationRow - Type string `json:"type"` - CodeFlow [][]SourceCodeLocationRow `json:"codeFlow,omitempty"` + Location + Finding string `json:"finding,omitempty"` + ScannerDescription string `json:"scannerDescription,omitempty"` + CodeFlow [][]Location `json:"codeFlow,omitempty"` } -type SourceCodeLocationRow struct { - File string `json:"file"` - LineColumn string `json:"lineColumn"` - Text string `json:"text"` +type Location struct { + File string `json:"file"` + StartLine int `json:"startLine,omitempty"` + StartColumn int `json:"startColumn,omitempty"` + EndLine int `json:"endLine,omitempty"` + EndColumn int `json:"endColumn,omitempty"` + Snippet string `json:"snippet,omitempty"` } type ComponentRow struct { @@ -94,9 +98,21 @@ type ComponentRow struct { } type CveRow struct { - Id string `json:"id"` - CvssV2 string `json:"cvssV2"` - CvssV3 string `json:"cvssV3"` + Id string `json:"id"` + CvssV2 string `json:"cvssV2"` + CvssV3 string `json:"cvssV3"` + Applicability *Applicability `json:"applicability,omitempty"` +} + +type Applicability struct { + Status string `json:"status"` + ScannerDescription string `json:"scannerDescription,omitempty"` + Evidence []Evidence `json:"evidence,omitempty"` +} + +type Evidence struct { + Location + Reason string `json:"reason,omitempty"` } type SimpleJsonError struct { diff --git a/xray/formats/table.go b/xray/formats/table.go index c099b058d..ea21c845a 100644 --- a/xray/formats/table.go +++ b/xray/formats/table.go @@ -127,19 +127,12 @@ type secretsTableRow struct { severity string `col-name:"Severity"` file string `col-name:"File"` lineColumn string `col-name:"Line:Column"` - text string `col-name:"Secret"` + secret string `col-name:"Secret"` } -type iacTableRow struct { +type iacOrSastTableRow struct { severity string `col-name:"Severity"` file string `col-name:"File"` lineColumn string `col-name:"Line:Column"` - text string `col-name:"Finding"` -} - -type sastTableRow struct { - severity string `col-name:"Severity"` - file string `col-name:"File"` - lineColumn string `col-name:"Line:Column"` - text string `col-name:"Finding"` + finding string `col-name:"Finding"` } diff --git a/xray/scangraph/scangraph.go b/xray/scangraph/scangraph.go index 435fe23db..22dc755f0 100644 --- a/xray/scangraph/scangraph.go +++ b/xray/scangraph/scangraph.go @@ -24,11 +24,20 @@ func RunScanGraphAndGetResults(params *ScanGraphParams) (*services.ScanResponse, // Remove scan type param if Xray version is under the minimum supported version params.xrayGraphScanParams.ScanType = "" } + + if params.xrayGraphScanParams.XscGitInfoContext != nil { + if params.xrayGraphScanParams.XscVersion, err = xrayManager.XscEnabled(); err != nil { + return nil, err + } + } + scanId, err := xrayManager.ScanGraph(*params.xrayGraphScanParams) if err != nil { return nil, err } - scanResult, err := xrayManager.GetScanGraphResults(scanId, params.XrayGraphScanParams().IncludeVulnerabilities, params.XrayGraphScanParams().IncludeLicenses) + + xscEnabled := params.xrayGraphScanParams.XscVersion != "" + scanResult, err := xrayManager.GetScanGraphResults(scanId, params.XrayGraphScanParams().IncludeVulnerabilities, params.XrayGraphScanParams().IncludeLicenses, xscEnabled) if err != nil { return nil, err } diff --git a/xray/utils/analyzermanager.go b/xray/utils/analyzermanager.go index 8dfda3e04..70767c655 100644 --- a/xray/utils/analyzermanager.go +++ b/xray/utils/analyzermanager.go @@ -8,6 +8,7 @@ import ( "os/exec" "path" "path/filepath" + "strings" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" @@ -15,34 +16,14 @@ import ( "github.com/jfrog/jfrog-client-go/utils/io/fileutils" "github.com/jfrog/jfrog-client-go/utils/log" "github.com/jfrog/jfrog-client-go/xray/services" -) - -type SarifLevel string - -const ( - Error SarifLevel = "error" - Warning SarifLevel = "warning" - Info SarifLevel = "info" - Note SarifLevel = "note" - None SarifLevel = "none" - - SeverityDefaultValue = "Medium" -) - -var ( - // All other values (include default) mapped as 'Medium' severity - levelToSeverity = map[SarifLevel]string{ - Error: "High", - Note: "Low", - None: "Unknown", - } + "github.com/owenrumney/go-sarif/v2/sarif" ) const ( EntitlementsMinVersion = "3.66.5" ApplicabilityFeatureId = "contextual_analysis" AnalyzerManagerZipName = "analyzerManager.zip" - defaultAnalyzerManagerVersion = "1.2.4.1953469" + defaultAnalyzerManagerVersion = "1.3.2.2005632" minAnalyzerManagerVersionForSast = "1.3" analyzerManagerDownloadPath = "xsc-gen-exe-analyzer-manager-local/v1" analyzerManagerDirName = "analyzerManager" @@ -91,26 +72,15 @@ var exitCodeErrorsMap = map[int]string{ unsupportedOsExitCode: "got unsupported operating system error from analyzer manager", } -type SourceCodeLocation struct { - File string - LineColumn string - Text string -} - -type SourceCodeScanResult struct { - SourceCodeLocation - Severity string - Type string - CodeFlow []*[]SourceCodeLocation -} - type ExtendedScanResults struct { - XrayResults []services.ScanResponse - ScannedTechnologies []coreutils.Technology - ApplicabilityScanResults map[string]ApplicabilityStatus - SecretsScanResults []SourceCodeScanResult - IacScanResults []SourceCodeScanResult - SastResults []SourceCodeScanResult + XrayResults []services.ScanResponse + XrayVersion string + ScannedTechnologies []coreutils.Technology + + ApplicabilityScanResults []*sarif.Run + SecretsScanResults []*sarif.Run + IacScanResults []*sarif.Run + SastScanResults []*sarif.Run EntitledForJas bool } @@ -120,23 +90,30 @@ func (e *ExtendedScanResults) getXrayScanResults() []services.ScanResponse { type AnalyzerManager struct { AnalyzerManagerFullPath string + MultiScanId string } func (am *AnalyzerManager) Exec(configFile, scanCommand, workingDir string, serverDetails *config.ServerDetails) (err error) { if err = SetAnalyzerManagerEnvVariables(serverDetails); err != nil { - return err + return } - cmd := exec.Command(am.AnalyzerManagerFullPath, scanCommand, configFile) + cmd := exec.Command(am.AnalyzerManagerFullPath, scanCommand, configFile, am.MultiScanId) defer func() { - if !cmd.ProcessState.Exited() { + if cmd.ProcessState != nil && !cmd.ProcessState.Exited() { if killProcessError := cmd.Process.Kill(); errorutils.CheckError(killProcessError) != nil { err = errors.Join(err, killProcessError) } } }() cmd.Dir = workingDir - err = cmd.Run() - return errorutils.CheckError(err) + output, err := cmd.CombinedOutput() + if err != nil { + if len(output) > 0 { + log.Debug(fmt.Sprintf("%s %q output: %s", workingDir, strings.Join(cmd.Args, " "), string(output))) + } + err = errorutils.CheckError(err) + } + return } func GetAnalyzerManagerDownloadPath() (string, error) { @@ -148,7 +125,7 @@ func GetAnalyzerManagerDownloadPath() (string, error) { } func GetAnalyzerManagerVersion() string { - if analyzerManagerVersion, exists := os.LookupEnv(jfrogCliAnalyzerManagerVersionEnvVariable); exists { + if analyzerManagerVersion := os.Getenv(jfrogCliAnalyzerManagerVersionEnvVariable); analyzerManagerVersion != "" { return analyzerManagerVersion } return defaultAnalyzerManagerVersion diff --git a/xray/utils/analyzermanager_test.go b/xray/utils/analyzermanager_test.go index 7daabaade..c8637a7e2 100644 --- a/xray/utils/analyzermanager_test.go +++ b/xray/utils/analyzermanager_test.go @@ -25,12 +25,10 @@ func TestGetResultFileName(t *testing.T) { {PhysicalLocation: &sarif.PhysicalLocation{ArtifactLocation: &sarif.ArtifactLocation{URI: &fileNameValue}}}, }}, expectedOutput: fileNameValue}, - {result: &sarif.Result{}, - expectedOutput: ""}, } for _, test := range tests { - assert.Equal(t, test.expectedOutput, GetResultFileName(test.result)) + assert.Equal(t, test.expectedOutput, GetLocationFileName(test.result.Locations[0])) } } @@ -67,12 +65,10 @@ func TestGetResultLocationInFile(t *testing.T) { StartColumn: nil, }}}}}, expectedOutput: ""}, - {result: &sarif.Result{}, - expectedOutput: ""}, } for _, test := range tests { - assert.Equal(t, test.expectedOutput, GetResultLocationInFile(test.result)) + assert.Equal(t, test.expectedOutput, GetStartLocationInFile(test.result.Locations[0])) } } @@ -83,13 +79,15 @@ func TestExtractRelativePath(t *testing.T) { expectedResult string }{ {secretPath: "file:///Users/user/Desktop/secrets_scanner/tests/req.nodejs/file.js", - projectPath: "Users/user/Desktop/secrets_scanner/", expectedResult: "/tests/req.nodejs/file.js"}, + projectPath: "Users/user/Desktop/secrets_scanner/", expectedResult: "tests/req.nodejs/file.js"}, {secretPath: "invalidSecretPath", projectPath: "Users/user/Desktop/secrets_scanner/", expectedResult: "invalidSecretPath"}, {secretPath: "", projectPath: "Users/user/Desktop/secrets_scanner/", expectedResult: ""}, {secretPath: "file:///Users/user/Desktop/secrets_scanner/tests/req.nodejs/file.js", - projectPath: "invalidProjectPath", expectedResult: "/Users/user/Desktop/secrets_scanner/tests/req.nodejs/file.js"}, + projectPath: "invalidProjectPath", expectedResult: "Users/user/Desktop/secrets_scanner/tests/req.nodejs/file.js"}, + {secretPath: "file:///private/Users/user/Desktop/secrets_scanner/tests/req.nodejs/file.js", + projectPath: "invalidProjectPath", expectedResult: "Users/user/Desktop/secrets_scanner/tests/req.nodejs/file.js"}, } for _, test := range tests { @@ -98,11 +96,11 @@ func TestExtractRelativePath(t *testing.T) { } func TestGetResultSeverity(t *testing.T) { - levelValueHigh := string(Error) - levelValueMedium := string(Warning) - levelValueMedium2 := string(Info) - levelValueLow := string(Note) - levelValueUnknown := string(None) + levelValueHigh := string(errorLevel) + levelValueMedium := string(warningLevel) + levelValueMedium2 := string(infoLevel) + levelValueLow := string(noteLevel) + levelValueUnknown := string(noneLevel) tests := []struct { result *sarif.Result diff --git a/xray/utils/auditbasicparams.go b/xray/utils/auditbasicparams.go index f8e3abba3..ff990e9fc 100644 --- a/xray/utils/auditbasicparams.go +++ b/xray/utils/auditbasicparams.go @@ -6,26 +6,26 @@ import ( ) type AuditBasicParams struct { - serverDetails *config.ServerDetails - outputFormat OutputFormat - progress ioUtils.ProgressMgr - directDependencies []string - excludeTestDependencies bool - useWrapper bool - insecureTls bool - pipRequirementsFile string - technologies []string - args []string - depsRepo string - ignoreConfigFile bool + serverDetails *config.ServerDetails + outputFormat OutputFormat + progress ioUtils.ProgressMgr + dependenciesForApplicabilityScan []string + excludeTestDependencies bool + useWrapper bool + insecureTls bool + pipRequirementsFile string + technologies []string + args []string + depsRepo string + ignoreConfigFile bool } func (abp *AuditBasicParams) DirectDependencies() []string { - return abp.directDependencies + return abp.dependenciesForApplicabilityScan } -func (abp *AuditBasicParams) AppendDirectDependencies(directDependencies []string) *AuditBasicParams { - abp.directDependencies = append(abp.directDependencies, directDependencies...) +func (abp *AuditBasicParams) AppendDependenciesForApplicabilityScan(directDependencies []string) *AuditBasicParams { + abp.dependenciesForApplicabilityScan = append(abp.dependenciesForApplicabilityScan, directDependencies...) return abp } diff --git a/xray/utils/resultstable.go b/xray/utils/resultstable.go index ef7a284cd..929bd8f5c 100644 --- a/xray/utils/resultstable.go +++ b/xray/utils/resultstable.go @@ -3,11 +3,13 @@ package utils import ( "fmt" "os" + "path/filepath" "sort" "strconv" "strings" "github.com/jfrog/gofrog/datastructures" + "github.com/owenrumney/go-sarif/v2/sarif" "golang.org/x/exp/maps" "golang.org/x/text/cases" "golang.org/x/text/language" @@ -25,6 +27,8 @@ const ( rootIndex = 0 directDependencyIndex = 1 directDependencyPathLength = 2 + nodeModules = "node_modules" + NpmPackageTypeIdentifier = "npm://" ) // PrintViolationsTable prints the violations in 4 tables: security violations, license compliance violations, operational risk violations and ignore rule URLs. @@ -87,8 +91,13 @@ func prepareViolations(violations []services.Violation, extendedResults *Extende switch violation.ViolationType { case "security": cves := convertCves(violation.Cves) - applicableValue := getApplicableCveValue(extendedResults, cves) - currSeverity := GetSeverity(violation.Severity, applicableValue) + if extendedResults.EntitledForJas { + for i := range cves { + cves[i].Applicability = getCveApplicabilityField(cves[i], extendedResults.ApplicabilityScanResults, violation.Components) + } + } + applicabilityStatus := getApplicableCveStatus(extendedResults.EntitledForJas, extendedResults.ApplicabilityScanResults, cves) + currSeverity := GetSeverity(violation.Severity, applicabilityStatus) jfrogResearchInfo := convertJfrogResearchInformation(violation.ExtendedInformation) for compIndex := 0; compIndex < len(impactedPackagesNames); compIndex++ { securityViolationsRows = append(securityViolationsRows, @@ -107,7 +116,7 @@ func prepareViolations(violations []services.Violation, extendedResults *Extende JfrogResearchInformation: jfrogResearchInfo, ImpactPaths: impactPaths[compIndex], Technology: coreutils.Technology(violation.Technology), - Applicable: printApplicableCveValue(applicableValue, isTable), + Applicable: printApplicableCveValue(applicabilityStatus, isTable), }, ) } @@ -204,8 +213,13 @@ func prepareVulnerabilities(vulnerabilities []services.Vulnerability, extendedRe return nil, err } cves := convertCves(vulnerability.Cves) - applicableValue := getApplicableCveValue(extendedResults, cves) - currSeverity := GetSeverity(vulnerability.Severity, applicableValue) + if extendedResults.EntitledForJas { + for i := range cves { + cves[i].Applicability = getCveApplicabilityField(cves[i], extendedResults.ApplicabilityScanResults, vulnerability.Components) + } + } + applicabilityStatus := getApplicableCveStatus(extendedResults.EntitledForJas, extendedResults.ApplicabilityScanResults, cves) + currSeverity := GetSeverity(vulnerability.Severity, applicabilityStatus) jfrogResearchInfo := convertJfrogResearchInformation(vulnerability.ExtendedInformation) for compIndex := 0; compIndex < len(impactedPackagesNames); compIndex++ { vulnerabilitiesRows = append(vulnerabilitiesRows, @@ -224,7 +238,7 @@ func prepareVulnerabilities(vulnerabilities []services.Vulnerability, extendedRe JfrogResearchInformation: jfrogResearchInfo, ImpactPaths: impactPaths[compIndex], Technology: coreutils.Technology(vulnerability.Technology), - Applicable: printApplicableCveValue(applicableValue, isTable), + Applicable: printApplicableCveValue(applicabilityStatus, isTable), }, ) } @@ -284,26 +298,33 @@ func PrepareLicenses(licenses []services.License) ([]formats.LicenseRow, error) } // Prepare secrets for all non-table formats (without style or emoji) -func PrepareSecrets(secrets []SourceCodeScanResult) []formats.SourceCodeRow { +func PrepareSecrets(secrets []*sarif.Run) []formats.SourceCodeRow { return prepareSecrets(secrets, false) } -func prepareSecrets(secrets []SourceCodeScanResult, isTable bool) []formats.SourceCodeRow { +func prepareSecrets(secrets []*sarif.Run, isTable bool) []formats.SourceCodeRow { var secretsRows []formats.SourceCodeRow - for _, secret := range secrets { - currSeverity := GetSeverity(secret.Severity, Applicable) - secretsRows = append(secretsRows, - formats.SourceCodeRow{ - Severity: currSeverity.printableTitle(isTable), - SeverityNumValue: currSeverity.numValue, - SourceCodeLocationRow: formats.SourceCodeLocationRow{ - File: secret.File, - LineColumn: secret.LineColumn, - Text: secret.Text, - }, - Type: secret.Type, - }, - ) + for _, secretRun := range secrets { + for _, secretResult := range secretRun.Results { + currSeverity := GetSeverity(GetResultSeverity(secretResult), Applicable) + for _, location := range secretResult.Locations { + secretsRows = append(secretsRows, + formats.SourceCodeRow{ + Severity: currSeverity.printableTitle(isTable), + Finding: GetResultMsgText(secretResult), + SeverityNumValue: currSeverity.numValue, + Location: formats.Location{ + File: GetRelativeLocationFileName(location, secretRun.Invocations), + StartLine: GetLocationStartLine(location), + StartColumn: GetLocationStartColumn(location), + EndLine: GetLocationEndLine(location), + EndColumn: GetLocationEndColumn(location), + Snippet: GetLocationSnippet(location), + }, + }, + ) + } + } } sort.Slice(secretsRows, func(i, j int) bool { @@ -313,7 +334,7 @@ func prepareSecrets(secrets []SourceCodeScanResult, isTable bool) []formats.Sour return secretsRows } -func PrintSecretsTable(secrets []SourceCodeScanResult, entitledForSecretsScan bool) error { +func PrintSecretsTable(secrets []*sarif.Run, entitledForSecretsScan bool) error { if entitledForSecretsScan { secretsRows := prepareSecrets(secrets, true) log.Output() @@ -324,26 +345,38 @@ func PrintSecretsTable(secrets []SourceCodeScanResult, entitledForSecretsScan bo } // Prepare iacs for all non-table formats (without style or emoji) -func PrepareIacs(iacs []SourceCodeScanResult) []formats.SourceCodeRow { +func PrepareIacs(iacs []*sarif.Run) []formats.SourceCodeRow { return prepareIacs(iacs, false) } -func prepareIacs(iacs []SourceCodeScanResult, isTable bool) []formats.SourceCodeRow { +func prepareIacs(iacs []*sarif.Run, isTable bool) []formats.SourceCodeRow { var iacRows []formats.SourceCodeRow - for _, iac := range iacs { - currSeverity := GetSeverity(iac.Severity, Applicable) - iacRows = append(iacRows, - formats.SourceCodeRow{ - Severity: currSeverity.printableTitle(isTable), - SeverityNumValue: currSeverity.numValue, - SourceCodeLocationRow: formats.SourceCodeLocationRow{ - File: iac.File, - LineColumn: iac.LineColumn, - Text: iac.Text, - }, - Type: iac.Type, - }, - ) + for _, iacRun := range iacs { + for _, iacResult := range iacRun.Results { + scannerDescription := "" + if rule, err := iacRun.GetRuleById(*iacResult.RuleID); err == nil { + scannerDescription = GetRuleFullDescription(rule) + } + currSeverity := GetSeverity(GetResultSeverity(iacResult), Applicable) + for _, location := range iacResult.Locations { + iacRows = append(iacRows, + formats.SourceCodeRow{ + Severity: currSeverity.printableTitle(isTable), + Finding: GetResultMsgText(iacResult), + ScannerDescription: scannerDescription, + SeverityNumValue: currSeverity.numValue, + Location: formats.Location{ + File: GetRelativeLocationFileName(location, iacRun.Invocations), + StartLine: GetLocationStartLine(location), + StartColumn: GetLocationStartColumn(location), + EndLine: GetLocationEndLine(location), + EndColumn: GetLocationEndColumn(location), + Snippet: GetLocationSnippet(location), + }, + }, + ) + } + } } sort.Slice(iacRows, func(i, j int) bool { @@ -353,37 +386,51 @@ func prepareIacs(iacs []SourceCodeScanResult, isTable bool) []formats.SourceCode return iacRows } -func PrintIacTable(iacs []SourceCodeScanResult, entitledForIacScan bool) error { +func PrintIacTable(iacs []*sarif.Run, entitledForIacScan bool) error { if entitledForIacScan { iacRows := prepareIacs(iacs, true) log.Output() - return coreutils.PrintTable(formats.ConvertToIacTableRow(iacRows), "Infrastructure as Code Vulnerabilities", + return coreutils.PrintTable(formats.ConvertToIacOrSastTableRow(iacRows), "Infrastructure as Code Vulnerabilities", "✨ No Infrastructure as Code vulnerabilities were found ✨", false) } return nil } -func PrepareSast(sasts []SourceCodeScanResult) []formats.SourceCodeRow { +func PrepareSast(sasts []*sarif.Run) []formats.SourceCodeRow { return prepareSast(sasts, false) } -func prepareSast(sasts []SourceCodeScanResult, isTable bool) []formats.SourceCodeRow { +func prepareSast(sasts []*sarif.Run, isTable bool) []formats.SourceCodeRow { var sastRows []formats.SourceCodeRow - for _, sast := range sasts { - currSeverity := GetSeverity(sast.Severity, Applicable) - sastRows = append(sastRows, - formats.SourceCodeRow{ - Severity: currSeverity.printableTitle(isTable), - SeverityNumValue: currSeverity.numValue, - SourceCodeLocationRow: formats.SourceCodeLocationRow{ - File: sast.File, - LineColumn: sast.LineColumn, - Text: sast.Text, - }, - Type: sast.Type, - CodeFlow: toSourceCodeCodeFlowRow(sast, isTable), - }, - ) + for _, sastRun := range sasts { + for _, sastResult := range sastRun.Results { + scannerDescription := "" + if rule, err := sastRun.GetRuleById(*sastResult.RuleID); err == nil { + scannerDescription = GetRuleFullDescription(rule) + } + currSeverity := GetSeverity(GetResultSeverity(sastResult), Applicable) + + for _, location := range sastResult.Locations { + codeFlows := GetLocationRelatedCodeFlowsFromResult(location, sastResult) + sastRows = append(sastRows, + formats.SourceCodeRow{ + Severity: currSeverity.printableTitle(isTable), + Finding: GetResultMsgText(sastResult), + ScannerDescription: scannerDescription, + SeverityNumValue: currSeverity.numValue, + Location: formats.Location{ + File: GetRelativeLocationFileName(location, sastRun.Invocations), + StartLine: GetLocationStartLine(location), + StartColumn: GetLocationStartColumn(location), + EndLine: GetLocationEndLine(location), + EndColumn: GetLocationEndColumn(location), + Snippet: GetLocationSnippet(location), + }, + CodeFlow: codeFlowToLocationFlow(codeFlows, sastRun.Invocations, isTable), + }, + ) + } + } } sort.Slice(sastRows, func(i, j int) bool { @@ -393,43 +440,40 @@ func prepareSast(sasts []SourceCodeScanResult, isTable bool) []formats.SourceCod return sastRows } -func toSourceCodeCodeFlowRow(result SourceCodeScanResult, isTable bool) (flows [][]formats.SourceCodeLocationRow) { +func codeFlowToLocationFlow(flows []*sarif.CodeFlow, invocations []*sarif.Invocation, isTable bool) (flowRows [][]formats.Location) { if isTable { // Not displaying in table return } - for _, flowStack := range result.CodeFlow { - rowFlow := []formats.SourceCodeLocationRow{} - for _, location := range *flowStack { - rowFlow = append(rowFlow, formats.SourceCodeLocationRow{ - File: location.File, - LineColumn: location.LineColumn, - Text: location.Text, - }) + for _, codeFlow := range flows { + for _, stackTrace := range codeFlow.ThreadFlows { + rowFlow := []formats.Location{} + for _, stackTraceEntry := range stackTrace.Locations { + rowFlow = append(rowFlow, formats.Location{ + File: GetRelativeLocationFileName(stackTraceEntry.Location, invocations), + StartLine: GetLocationStartLine(stackTraceEntry.Location), + StartColumn: GetLocationStartColumn(stackTraceEntry.Location), + EndLine: GetLocationEndLine(stackTraceEntry.Location), + EndColumn: GetLocationEndColumn(stackTraceEntry.Location), + Snippet: GetLocationSnippet(stackTraceEntry.Location), + }) + } + flowRows = append(flowRows, rowFlow) } - flows = append(flows, rowFlow) } return } -func PrintSastTable(sast []SourceCodeScanResult, entitledForSastScan bool) error { +func PrintSastTable(sast []*sarif.Run, entitledForSastScan bool) error { if entitledForSastScan { sastRows := prepareSast(sast, true) log.Output() - return coreutils.PrintTable(formats.ConvertToSastTableRow(sastRows), "Static Application Security Testing (SAST)", + return coreutils.PrintTable(formats.ConvertToIacOrSastTableRow(sastRows), "Static Application Security Testing (SAST)", "✨ No Static Application Security Testing vulnerabilities were found ✨", false) } return nil } -func convertCves(cves []services.Cve) []formats.CveRow { - var cveRows []formats.CveRow - for _, cveObj := range cves { - cveRows = append(cveRows, formats.CveRow{Id: cveObj.Id, CvssV2: cveObj.CvssV2Score, CvssV3: cveObj.CvssV3Score}) - } - return cveRows -} - func convertJfrogResearchInformation(extendedInfo *services.ExtendedInformation) *formats.JfrogResearchInformation { if extendedInfo == nil { return nil @@ -873,33 +917,86 @@ func GetUniqueKey(vulnerableDependency, vulnerableVersion, xrayID string, fixVer return strings.Join([]string{vulnerableDependency, vulnerableVersion, xrayID, strconv.FormatBool(fixVersionExist)}, ":") } +func convertCves(cves []services.Cve) []formats.CveRow { + var cveRows []formats.CveRow + for _, cveObj := range cves { + cveRows = append(cveRows, formats.CveRow{Id: cveObj.Id, CvssV2: cveObj.CvssV2Score, CvssV3: cveObj.CvssV3Score}) + } + return cveRows +} + // If at least one cve is applicable - final value is applicable // Else if at least one cve is undetermined - final value is undetermined // Else (case when all cves aren't applicable) -> final value is not applicable -func getApplicableCveValue(extendedResults *ExtendedScanResults, xrayCves []formats.CveRow) ApplicabilityStatus { - if !extendedResults.EntitledForJas || len(extendedResults.ApplicabilityScanResults) == 0 { +func getApplicableCveStatus(entitledForJas bool, applicabilityScanResults []*sarif.Run, cves []formats.CveRow) ApplicabilityStatus { + if !entitledForJas || len(applicabilityScanResults) == 0 { return NotScanned } - - if len(xrayCves) == 0 { + if len(cves) == 0 { return ApplicabilityUndetermined } - cveExistsInResult := false - finalApplicableValue := NotApplicable - for _, cve := range xrayCves { - if currentCveApplicableValue, exists := extendedResults.ApplicabilityScanResults[cve.Id]; exists { - cveExistsInResult = true - if currentCveApplicableValue == Applicable { - return currentCveApplicableValue - } else if currentCveApplicableValue == ApplicabilityUndetermined { - finalApplicableValue = currentCveApplicableValue + foundUndetermined := false + for _, cve := range cves { + if cve.Applicability != nil { + if cve.Applicability.Status == string(Applicable) { + return Applicable + } + if cve.Applicability.Status == string(ApplicabilityUndetermined) { + foundUndetermined = true + } + } + } + if foundUndetermined { + return ApplicabilityUndetermined + } + return NotApplicable +} + +func getCveApplicabilityField(cve formats.CveRow, applicabilityScanResults []*sarif.Run, components map[string]services.Component) *formats.Applicability { + if len(applicabilityScanResults) == 0 { + return nil + } + + applicability := formats.Applicability{} + resultFound := false + for _, applicabilityRun := range applicabilityScanResults { + result, _ := applicabilityRun.GetResultByRuleId(CveToApplicabilityRuleId(cve.Id)) + if result == nil { + continue + } + resultFound = true + rule, _ := applicabilityRun.GetRuleById(CveToApplicabilityRuleId(cve.Id)) + if rule != nil { + applicability.ScannerDescription = GetRuleFullDescription(rule) + } + // Add new evidences from locations + for _, location := range result.Locations { + fileName := GetRelativeLocationFileName(location, applicabilityRun.Invocations) + if shouldDisqualifyEvidence(components, fileName) { + continue } + applicability.Evidence = append(applicability.Evidence, formats.Evidence{ + Location: formats.Location{ + File: fileName, + StartLine: GetLocationStartLine(location), + StartColumn: GetLocationStartColumn(location), + EndLine: GetLocationEndLine(location), + EndColumn: GetLocationEndColumn(location), + Snippet: GetLocationSnippet(location), + }, + Reason: GetResultMsgText(result), + }) } } - if cveExistsInResult { - return finalApplicableValue + switch { + case !resultFound: + applicability.Status = string(ApplicabilityUndetermined) + case len(applicability.Evidence) == 0: + applicability.Status = string(NotApplicable) + default: + applicability.Status = string(Applicable) } - return ApplicabilityUndetermined + return &applicability } func printApplicableCveValue(applicableValue ApplicabilityStatus, isTable bool) string { @@ -912,3 +1009,39 @@ func printApplicableCveValue(applicableValue ApplicabilityStatus, isTable bool) } return string(applicableValue) } + +// Relevant only when "third-party-contextual-analysis" flag is on, +// which mean we scan the environment folders as well (node_modules for example...) +// When a certain package is reported applicable, and the evidence found +// is inside the source code of the same package, we should disqualify it. +// +// For example, +// Cve applicability was found inside the 'mquery' package. +// filePath = myProject/node_modules/mquery/badCode.js , disqualify = True. +// Disqualify the above evidence, as the reported applicability is used inside its own package. +// +// filePath = myProject/node_modules/mpath/badCode.js , disqualify = False. +// Found use of a badCode inside the node_modules from a different package, report applicable. +func shouldDisqualifyEvidence(components map[string]services.Component, evidenceFilePath string) (disqualify bool) { + for key := range components { + if !strings.HasPrefix(key, NpmPackageTypeIdentifier) { + return + } + dependencyName := extractDependencyNameFromComponent(key, NpmPackageTypeIdentifier) + // Check both Unix & Windows paths. + if strings.Contains(evidenceFilePath, nodeModules+"/"+dependencyName) || strings.Contains(evidenceFilePath, filepath.Join(nodeModules, dependencyName)) { + return true + } + } + return +} + +func extractDependencyNameFromComponent(key string, techIdentifier string) (dependencyName string) { + packageAndVersion := strings.TrimPrefix(key, techIdentifier) + split := strings.Split(packageAndVersion, ":") + if len(split) < 2 { + return + } + dependencyName = split[0] + return +} diff --git a/xray/utils/resultstable_test.go b/xray/utils/resultstable_test.go index ddb265fac..18c41d994 100644 --- a/xray/utils/resultstable_test.go +++ b/xray/utils/resultstable_test.go @@ -3,9 +3,11 @@ package utils import ( "errors" "fmt" - "github.com/jfrog/jfrog-cli-core/v2/xray/formats" "testing" + "github.com/jfrog/jfrog-cli-core/v2/xray/formats" + "github.com/owenrumney/go-sarif/v2/sarif" + "github.com/jfrog/jfrog-client-go/xray/services" "github.com/stretchr/testify/assert" ) @@ -426,8 +428,9 @@ func TestGetSeveritiesFormat(t *testing.T) { func TestGetApplicableCveValue(t *testing.T) { testCases := []struct { scanResults *ExtendedScanResults - cves []formats.CveRow + cves []services.Cve expectedResult ApplicabilityStatus + expectedCves []formats.CveRow }{ { scanResults: &ExtendedScanResults{EntitledForJas: false}, @@ -435,54 +438,100 @@ func TestGetApplicableCveValue(t *testing.T) { }, { scanResults: &ExtendedScanResults{ - ApplicabilityScanResults: map[string]ApplicabilityStatus{"testCve1": Applicable, "testCve2": NotApplicable}, - EntitledForJas: true, + ApplicabilityScanResults: []*sarif.Run{ + getRunWithDummyResults( + getDummyResultWithOneLocation("fileName1", 0, 1, "snippet1", "applic_testCve1", "info"), + getDummyPassingResult("applic_testCve2"), + ), + }, + EntitledForJas: true, }, cves: nil, expectedResult: ApplicabilityUndetermined, + expectedCves: nil, }, { scanResults: &ExtendedScanResults{ - ApplicabilityScanResults: map[string]ApplicabilityStatus{"testCve1": NotApplicable, "testCve2": Applicable}, - EntitledForJas: true, + ApplicabilityScanResults: []*sarif.Run{ + getRunWithDummyResults( + getDummyPassingResult("applic_testCve1"), + getDummyResultWithOneLocation("fileName2", 1, 0, "snippet2", "applic_testCve2", "warning"), + ), + }, + EntitledForJas: true, }, - cves: []formats.CveRow{{Id: "testCve2"}}, + cves: []services.Cve{{Id: "testCve2"}}, expectedResult: Applicable, + expectedCves: []formats.CveRow{{Id: "testCve2", Applicability: &formats.Applicability{Status: string(Applicable)}}}, }, { scanResults: &ExtendedScanResults{ - ApplicabilityScanResults: map[string]ApplicabilityStatus{"testCve1": NotApplicable, "testCve2": Applicable}, - EntitledForJas: true, + ApplicabilityScanResults: []*sarif.Run{ + getRunWithDummyResults( + getDummyPassingResult("applic_testCve1"), + getDummyResultWithOneLocation("fileName3", 0, 1, "snippet3", "applic_testCve2", "info"), + ), + }, + EntitledForJas: true, }, - cves: []formats.CveRow{{Id: "testCve3"}}, + cves: []services.Cve{{Id: "testCve3"}}, expectedResult: ApplicabilityUndetermined, + expectedCves: []formats.CveRow{{Id: "testCve3"}}, }, { scanResults: &ExtendedScanResults{ - ApplicabilityScanResults: map[string]ApplicabilityStatus{"testCve1": NotApplicable, "testCve2": NotApplicable}, - EntitledForJas: true}, - cves: []formats.CveRow{{Id: "testCve1"}, {Id: "testCve2"}}, + ApplicabilityScanResults: []*sarif.Run{ + getRunWithDummyResults( + getDummyPassingResult("applic_testCve1"), + getDummyPassingResult("applic_testCve2"), + ), + }, + EntitledForJas: true, + }, + cves: []services.Cve{{Id: "testCve1"}, {Id: "testCve2"}}, expectedResult: NotApplicable, + expectedCves: []formats.CveRow{{Id: "testCve1", Applicability: &formats.Applicability{Status: string(NotApplicable)}}, {Id: "testCve2", Applicability: &formats.Applicability{Status: string(NotApplicable)}}}, }, { scanResults: &ExtendedScanResults{ - ApplicabilityScanResults: map[string]ApplicabilityStatus{"testCve1": NotApplicable, "testCve2": Applicable}, - EntitledForJas: true, + ApplicabilityScanResults: []*sarif.Run{ + getRunWithDummyResults( + getDummyPassingResult("applic_testCve1"), + getDummyResultWithOneLocation("fileName4", 1, 0, "snippet", "applic_testCve2", "warning"), + ), + }, + EntitledForJas: true, }, - cves: []formats.CveRow{{Id: "testCve1"}, {Id: "testCve2"}}, + cves: []services.Cve{{Id: "testCve1"}, {Id: "testCve2"}}, expectedResult: Applicable, + expectedCves: []formats.CveRow{{Id: "testCve1", Applicability: &formats.Applicability{Status: string(NotApplicable)}}, {Id: "testCve2", Applicability: &formats.Applicability{Status: string(Applicable)}}}, }, { scanResults: &ExtendedScanResults{ - ApplicabilityScanResults: map[string]ApplicabilityStatus{"testCve1": NotApplicable, "testCve2": ApplicabilityUndetermined}, - EntitledForJas: true}, - cves: []formats.CveRow{{Id: "testCve1"}, {Id: "testCve2"}}, + ApplicabilityScanResults: []*sarif.Run{ + getRunWithDummyResults(getDummyPassingResult("applic_testCve1")), + }, + EntitledForJas: true}, + cves: []services.Cve{{Id: "testCve1"}, {Id: "testCve2"}}, expectedResult: ApplicabilityUndetermined, + expectedCves: []formats.CveRow{{Id: "testCve1", Applicability: &formats.Applicability{Status: string(NotApplicable)}}, {Id: "testCve2"}}, }, } for _, testCase := range testCases { - assert.Equal(t, testCase.expectedResult, getApplicableCveValue(testCase.scanResults, testCase.cves)) + cves := convertCves(testCase.cves) + for i := range cves { + cves[i].Applicability = getCveApplicabilityField(cves[i], testCase.scanResults.ApplicabilityScanResults, nil) + } + applicableValue := getApplicableCveStatus(testCase.scanResults.EntitledForJas, testCase.scanResults.ApplicabilityScanResults, cves) + assert.Equal(t, testCase.expectedResult, applicableValue) + if assert.True(t, len(testCase.expectedCves) == len(cves)) { + for i := range cves { + if testCase.expectedCves[i].Applicability != nil && assert.NotNil(t, cves[i].Applicability) { + assert.Equal(t, testCase.expectedCves[i].Applicability.Status, cves[i].Applicability.Status) + } + } + } } } @@ -588,6 +637,43 @@ func TestSortVulnerabilityOrViolationRows(t *testing.T) { } } +func TestShouldDisqualifyEvidence(t *testing.T) { + testCases := []struct { + name string + component map[string]services.Component + filePath string + disqualify bool + }{ + { + name: "package folders", + component: map[string]services.Component{"npm://protobufjs:6.11.2": {}}, + filePath: "file:///Users/jfrog/test/node_modules/protobufjs/src/badCode.js", + disqualify: true, + }, { + name: "nested folders", + component: map[string]services.Component{"npm://protobufjs:6.11.2": {}}, + filePath: "file:///Users/jfrog/test/node_modules/someDep/node_modules/protobufjs/src/badCode.js", + disqualify: true, + }, { + name: "applicability in node modules", + component: map[string]services.Component{"npm://protobufjs:6.11.2": {}}, + filePath: "file:///Users/jfrog/test/node_modules/mquery/src/badCode.js", + disqualify: false, + }, { + // Only npm supported + name: "not npm", + component: map[string]services.Component{"yarn://protobufjs:6.11.2": {}}, + filePath: "file:///Users/jfrog/test/node_modules/protobufjs/src/badCode.js", + disqualify: false, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.disqualify, shouldDisqualifyEvidence(tc.component, tc.filePath)) + }) + } +} + func newBoolPtr(v bool) *bool { return &v } diff --git a/xray/utils/resultwriter.go b/xray/utils/resultwriter.go index e91b0fead..5c1100190 100644 --- a/xray/utils/resultwriter.go +++ b/xray/utils/resultwriter.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/json" "fmt" - "os" "strconv" "strings" @@ -28,27 +27,13 @@ const ( Sarif OutputFormat = "sarif" ) -const missingCveScore = "0" +const MissingCveScore = "0" const maxPossibleCve = 10.0 var OutputFormats = []string{string(Table), string(Json), string(SimpleJson), string(Sarif)} var CurationOutputFormats = []string{string(Table), string(Json)} -type sarifProperties struct { - Applicable string - Cves string - Headline string - Severity string - Description string - MarkdownDescription string - XrayID string - File string - LineColumn string - Type string - CodeFlows [][]formats.SourceCodeLocationRow -} - type ResultsWriter struct { // The scan results. results *ExtendedScanResults @@ -130,7 +115,7 @@ func (rw *ResultsWriter) PrintScanResults() error { case Json: return PrintJson(rw.results.getXrayScanResults()) case Sarif: - sarifFile, err := rw.generateSarifFileFromScan(false, "JFrog Security", coreutils.JFrogComUrl+"xray/") + sarifFile, err := rw.generateSarifContentFromResults(false) if err != nil { return err } @@ -138,25 +123,6 @@ func (rw *ResultsWriter) PrintScanResults() error { } return nil } - -func (rw *ResultsWriter) generateSarifFileFromScan(markdownOutput bool, scanningTool, toolURI string) (string, error) { - report, err := sarif.New(sarif.Version210) - if err != nil { - return "", errorutils.CheckError(err) - } - run := sarif.NewRunWithInformationURI(scanningTool, toolURI) - if err = rw.convertScanToSarif(run, markdownOutput); err != nil { - return "", err - } - report.AddRun(run) - out, err := json.Marshal(report) - if err != nil { - return "", errorutils.CheckError(err) - } - - return clientUtils.IndentJson(out), nil -} - func (rw *ResultsWriter) printScanResultsTables() (err error) { printMessages(rw.messages) violations, vulnerabilities, licenses := SplitScanResults(rw.results.getXrayScanResults()) @@ -190,22 +156,160 @@ func (rw *ResultsWriter) printScanResultsTables() (err error) { if !IsSastSupported() { return } - return PrintSastTable(rw.results.SastResults, rw.results.EntitledForJas) + return PrintSastTable(rw.results.SastScanResults, rw.results.EntitledForJas) } -func (rw *ResultsWriter) convertScanToSimpleJson(simplifiedOutput bool) (formats.SimpleJsonResults, error) { - extendedResults := rw.results - violations, vulnerabilities, licenses := SplitScanResults(extendedResults.XrayResults) +func printMessages(messages []string) { + if len(messages) > 0 { + log.Output() + } + for _, m := range messages { + printMessage(m) + } +} + +func printMessage(message string) { + log.Output("💬" + message) +} + +func (rw *ResultsWriter) generateSarifContentFromResults(markdownOutput bool) (sarifStr string, err error) { + report, err := NewReport() + if err != nil { + return + } + xrayRun, err := rw.convertXrayResponsesToSarifRun(rw.results, rw.isMultipleRoots, rw.includeLicenses, markdownOutput) + if err != nil { + return + } + + report.Runs = append(report.Runs, xrayRun) + report.Runs = append(report.Runs, rw.results.ApplicabilityScanResults...) + report.Runs = append(report.Runs, rw.results.IacScanResults...) + report.Runs = append(report.Runs, rw.results.SecretsScanResults...) + report.Runs = append(report.Runs, rw.results.SastScanResults...) + + out, err := json.Marshal(report) + if err != nil { + return "", errorutils.CheckError(err) + } + + return clientUtils.IndentJson(out), nil +} + +func (rw *ResultsWriter) convertXrayResponsesToSarifRun(extendedResults *ExtendedScanResults, isMultipleRoots, includeLicenses, markdownOutput bool) (run *sarif.Run, err error) { + xrayJson, err := rw.convertXrayScanToSimpleJson(true) + if err != nil { + return + } + xrayRun := sarif.NewRunWithInformationURI("JFrog Xray Sca", "https://jfrog.com/xray/") + xrayRun.Tool.Driver.Version = &extendedResults.XrayVersion + if len(xrayJson.Vulnerabilities) > 0 || len(xrayJson.SecurityViolations) > 0 { + if err = extractXrayIssuesToSarifRun(xrayRun, xrayJson, markdownOutput); err != nil { + return + } + } + run = xrayRun + return +} + +func extractXrayIssuesToSarifRun(run *sarif.Run, xrayJson formats.SimpleJsonResults, markdownOutput bool) error { + for _, vulnerability := range xrayJson.Vulnerabilities { + if err := addXrayCveIssueToSarifRun( + vulnerability.Cves, + vulnerability.IssueId, + vulnerability.Severity, + vulnerability.Technology.GetPackageDescriptor(), + vulnerability.Components, + vulnerability.Applicable, + vulnerability.ImpactedDependencyName, + vulnerability.ImpactedDependencyVersion, + vulnerability.Summary, + vulnerability.FixedVersions, + markdownOutput, + run, + ); err != nil { + return err + } + } + for _, violation := range xrayJson.SecurityViolations { + if err := addXrayCveIssueToSarifRun( + violation.Cves, + violation.IssueId, + violation.Severity, + violation.Technology.GetPackageDescriptor(), + violation.Components, + violation.Applicable, + violation.ImpactedDependencyName, + violation.ImpactedDependencyVersion, + violation.Summary, + violation.FixedVersions, + markdownOutput, + run, + ); err != nil { + return err + } + } + for _, license := range xrayJson.LicensesViolations { + msg := getVulnerabilityOrViolationSarifHeadline(license.LicenseKey, license.ImpactedDependencyName, license.ImpactedDependencyVersion) + if rule, isNewRule := addResultToSarifRun(license.LicenseKey, msg, license.Severity, nil, run); isNewRule { + rule.WithDescription("License watch violations") + } + } + return nil +} + +func addXrayCveIssueToSarifRun(cves []formats.CveRow, issueId, severity, file string, components []formats.ComponentRow, applicable, impactedDependencyName, impactedDependencyVersion, summary string, fixedVersions []string, markdownOutput bool, run *sarif.Run) error { + maxCveScore, err := findMaxCVEScore(cves) + if err != nil { + return err + } + cveId := GetIssueIdentifier(cves, issueId) + msg := getVulnerabilityOrViolationSarifHeadline(impactedDependencyName, impactedDependencyVersion, cveId) + location := sarif.NewLocation().WithPhysicalLocation(sarif.NewPhysicalLocation().WithArtifactLocation(sarif.NewArtifactLocation().WithUri(file))) + + if rule, isNewRule := addResultToSarifRun(cveId, msg, severity, location, run); isNewRule { + cveRuleProperties := sarif.NewPropertyBag() + if maxCveScore != MissingCveScore { + cveRuleProperties.Add("security-severity", maxCveScore) + } + rule.WithProperties(cveRuleProperties.Properties) + if markdownOutput { + formattedDirectDependencies, err := getDirectDependenciesFormatted(components) + if err != nil { + return err + } + markdownDescription := getSarifTableDescription(formattedDirectDependencies, maxCveScore, applicable, fixedVersions) + "\n" + rule.WithMarkdownHelp(markdownDescription) + } else { + rule.WithDescription(summary) + } + } + return nil +} + +func addResultToSarifRun(issueId, msg, severity string, location *sarif.Location, run *sarif.Run) (rule *sarif.ReportingDescriptor, isNewRule bool) { + if rule, _ = run.GetRuleById(issueId); rule == nil { + isNewRule = true + rule = run.AddRule(issueId) + } + if result := run.CreateResultForRule(issueId).WithMessage(sarif.NewTextMessage(msg)).WithLevel(ConvertToSarifLevel(severity)); location != nil { + result.AddLocation(location) + } + return +} + +func (rw *ResultsWriter) convertXrayScanToSimpleJson(simplifiedOutput bool) (formats.SimpleJsonResults, error) { + violations, vulnerabilities, licenses := SplitScanResults(rw.results.XrayResults) jsonTable := formats.SimpleJsonResults{} if len(vulnerabilities) > 0 { - vulJsonTable, err := PrepareVulnerabilities(vulnerabilities, extendedResults, rw.isMultipleRoots, simplifiedOutput) + vulJsonTable, err := PrepareVulnerabilities(vulnerabilities, rw.results, rw.isMultipleRoots, simplifiedOutput) if err != nil { return formats.SimpleJsonResults{}, err } jsonTable.Vulnerabilities = vulJsonTable } if len(violations) > 0 { - secViolationsJsonTable, licViolationsJsonTable, opRiskViolationsJsonTable, err := PrepareViolations(violations, extendedResults, rw.isMultipleRoots, simplifiedOutput) + secViolationsJsonTable, licViolationsJsonTable, opRiskViolationsJsonTable, err := PrepareViolations(violations, rw.results, rw.isMultipleRoots, simplifiedOutput) if err != nil { return formats.SimpleJsonResults{}, err } @@ -213,18 +317,6 @@ func (rw *ResultsWriter) convertScanToSimpleJson(simplifiedOutput bool) (formats jsonTable.LicensesViolations = licViolationsJsonTable jsonTable.OperationalRiskViolations = opRiskViolationsJsonTable } - if len(extendedResults.SecretsScanResults) > 0 { - secretsRows := PrepareSecrets(extendedResults.SecretsScanResults) - jsonTable.Secrets = secretsRows - } - if len(extendedResults.IacScanResults) > 0 { - iacRows := PrepareIacs(extendedResults.IacScanResults) - jsonTable.Iacs = iacRows - } - if len(extendedResults.SastResults) > 0 { - sastRows := PrepareSast(extendedResults.SastResults) - jsonTable.Sast = sastRows - } if rw.includeLicenses { licJsonTable, err := PrepareLicenses(licenses) if err != nil { @@ -232,194 +324,49 @@ func (rw *ResultsWriter) convertScanToSimpleJson(simplifiedOutput bool) (formats } jsonTable.Licenses = licJsonTable } - jsonTable.Errors = rw.simpleJsonError return jsonTable, nil } -func (rw *ResultsWriter) convertScanToSarif(run *sarif.Run, markdownOutput bool) error { - jsonTable, err := rw.convertScanToSimpleJson(markdownOutput) +func (rw *ResultsWriter) convertScanToSimpleJson(simplifiedOutput bool) (formats.SimpleJsonResults, error) { + jsonTable, err := rw.convertXrayScanToSimpleJson(simplifiedOutput) if err != nil { - return err + return formats.SimpleJsonResults{}, err } - if len(jsonTable.Vulnerabilities) > 0 || len(jsonTable.SecurityViolations) > 0 { - if err = convertToVulnerabilityOrViolationSarif(run, &jsonTable, markdownOutput); err != nil { - return err - } - } - return convertToSourceCodeResultSarif(run, &jsonTable, markdownOutput) -} - -func printMessages(messages []string) { - if len(messages) > 0 { - log.Output() - } - for _, m := range messages { - printMessage(m) + if len(rw.results.SecretsScanResults) > 0 { + jsonTable.Secrets = PrepareSecrets(rw.results.SecretsScanResults) } -} - -func printMessage(message string) { - log.Output("💬" + message) -} -func convertToVulnerabilityOrViolationSarif(run *sarif.Run, jsonTable *formats.SimpleJsonResults, markdownOutput bool) error { - if len(jsonTable.SecurityViolations) > 0 { - return convertViolationsToSarif(jsonTable, run, markdownOutput) + if len(rw.results.IacScanResults) > 0 { + jsonTable.Iacs = PrepareIacs(rw.results.IacScanResults) } - return convertVulnerabilitiesToSarif(jsonTable, run, markdownOutput) -} - -func convertToSourceCodeResultSarif(run *sarif.Run, jsonTable *formats.SimpleJsonResults, markdownOutput bool) (err error) { - for _, secret := range jsonTable.Secrets { - properties := getSourceCodeProperties(secret, markdownOutput, Secrets) - if err = addPropertiesToSarifRun(run, &properties); err != nil { - return - } - } - - for _, iac := range jsonTable.Iacs { - properties := getSourceCodeProperties(iac, markdownOutput, IaC) - if err = addPropertiesToSarifRun(run, &properties); err != nil { - return - } + if len(rw.results.SastScanResults) > 0 { + jsonTable.Sast = PrepareSast(rw.results.SastScanResults) } + jsonTable.Errors = rw.simpleJsonError - for _, sast := range jsonTable.Sast { - properties := getSourceCodeProperties(sast, markdownOutput, Sast) - if err = addPropertiesToSarifRun(run, &properties); err != nil { - return - } - } - return + return jsonTable, nil } -func getSourceCodeProperties(sourceCodeIssue formats.SourceCodeRow, markdownOutput bool, scanType JasScanType) sarifProperties { - file := strings.TrimPrefix(sourceCodeIssue.File, string(os.PathSeparator)) - mapSeverityToScore := map[string]string{ - "": "0.0", - "unknown": "0.0", - "low": "3.9", - "medium": "6.9", - "high": "8.9", - "critical": "10", - } - severity := mapSeverityToScore[strings.ToLower(sourceCodeIssue.Severity)] - - headline := "" - secretOrFinding := "" - switch scanType { - case IaC: - headline = "Infrastructure as Code Vulnerability" - secretOrFinding = "Finding" - case Sast: - headline = sourceCodeIssue.Text - secretOrFinding = "Finding" - case Secrets: - headline = "Potential Secret Exposed" - secretOrFinding = "Secret" - } - - markdownDescription := "" - if markdownOutput { - headerRow := fmt.Sprintf("| Severity | File | Line:Column | %s |\n", secretOrFinding) - separatorRow := "| :---: | :---: | :---: | :---: |\n" - tableHeader := headerRow + separatorRow - markdownDescription = tableHeader + fmt.Sprintf("| %s | %s | %s | %s |", sourceCodeIssue.Severity, file, sourceCodeIssue.LineColumn, sourceCodeIssue.Text) - } - return sarifProperties{ - Headline: headline, - Severity: severity, - Description: sourceCodeIssue.Text, - MarkdownDescription: markdownDescription, - File: file, - LineColumn: sourceCodeIssue.LineColumn, - Type: sourceCodeIssue.Type, - CodeFlows: sourceCodeIssue.CodeFlow, - } -} - -func getCves(cvesRow []formats.CveRow, issueId string) string { - var cvesStr string +func GetIssueIdentifier(cvesRow []formats.CveRow, issueId string) string { + var identifier string if len(cvesRow) != 0 { var cvesBuilder strings.Builder for _, cve := range cvesRow { cvesBuilder.WriteString(cve.Id + ", ") } - cvesStr = strings.TrimSuffix(cvesBuilder.String(), ", ") + identifier = strings.TrimSuffix(cvesBuilder.String(), ", ") } - if cvesStr == "" { - cvesStr = issueId + if identifier == "" { + identifier = issueId } - return cvesStr + return identifier } func getVulnerabilityOrViolationSarifHeadline(depName, version, key string) string { return fmt.Sprintf("[%s] %s %s", key, depName, version) } -func convertViolationsToSarif(jsonTable *formats.SimpleJsonResults, run *sarif.Run, markdownOutput bool) error { - for _, violation := range jsonTable.SecurityViolations { - properties, err := getViolatedDepsSarifProps(violation, markdownOutput) - if err != nil { - return err - } - if err = addPropertiesToSarifRun(run, &properties); err != nil { - return err - } - } - for _, license := range jsonTable.LicensesViolations { - if err := addPropertiesToSarifRun(run, - &sarifProperties{ - Severity: license.Severity, - Headline: getVulnerabilityOrViolationSarifHeadline(license.LicenseKey, license.ImpactedDependencyName, license.ImpactedDependencyVersion)}); err != nil { - return err - } - } - - return nil -} - -func getViolatedDepsSarifProps(vulnerabilityRow formats.VulnerabilityOrViolationRow, markdownOutput bool) (sarifProperties, error) { - cves := getCves(vulnerabilityRow.Cves, vulnerabilityRow.IssueId) - headline := getVulnerabilityOrViolationSarifHeadline(vulnerabilityRow.ImpactedDependencyName, vulnerabilityRow.ImpactedDependencyVersion, cves) - maxCveScore, err := findMaxCVEScore(vulnerabilityRow.Cves) - if err != nil { - return sarifProperties{}, err - } - formattedDirectDependencies, err := getDirectDependenciesFormatted(vulnerabilityRow.Components) - if err != nil { - return sarifProperties{}, err - } - markdownDescription := "" - if markdownOutput { - markdownDescription = getSarifTableDescription(formattedDirectDependencies, maxCveScore, vulnerabilityRow.Applicable, vulnerabilityRow.FixedVersions) + "\n" - } - return sarifProperties{ - Applicable: vulnerabilityRow.Applicable, - Cves: cves, - Headline: headline, - Severity: maxCveScore, - Description: vulnerabilityRow.Summary, - MarkdownDescription: markdownDescription, - File: vulnerabilityRow.Technology.GetPackageDescriptor(), - }, err -} - -func convertVulnerabilitiesToSarif(jsonTable *formats.SimpleJsonResults, run *sarif.Run, simplifiedOutput bool) error { - for _, vulnerability := range jsonTable.Vulnerabilities { - properties, err := getViolatedDepsSarifProps(vulnerability, simplifiedOutput) - if err != nil { - return err - } - if err = addPropertiesToSarifRun(run, &properties); err != nil { - return err - } - } - - return nil -} - func getDirectDependenciesFormatted(directDependencies []formats.ComponentRow) (string, error) { var formattedDirectDependencies strings.Builder for _, dependency := range directDependencies { @@ -443,92 +390,6 @@ func getSarifTableDescription(formattedDirectDependencies, maxCveScore, applicab maxCveScore, applicable, formattedDirectDependencies, descriptionFixVersions) } -// Adding the Xray scan results details to the sarif struct, for each issue found in the scan -func addPropertiesToSarifRun(run *sarif.Run, properties *sarifProperties) error { - pb := sarif.NewPropertyBag() - if properties.Severity != missingCveScore { - pb.Add("security-severity", properties.Severity) - } - description := properties.Description - markdownDescription := properties.MarkdownDescription - if markdownDescription != "" { - description = "" - } - location, err := getSarifLocation(properties.File, properties.LineColumn) - if err != nil { - return err - } - codeFlows, err := getCodeFlowProperties(properties) - if err != nil { - return err - } - ruleID := generateSarifRuleID(properties) - run.AddRule(ruleID). - WithDescription(description). - WithProperties(pb.Properties). - WithMarkdownHelp(markdownDescription) - run.CreateResultForRule(ruleID). - WithCodeFlows(codeFlows). - WithMessage(sarif.NewTextMessage(properties.Headline)). - AddLocation(location) - return nil -} - -func getSarifLocation(file, lineCol string) (location *sarif.Location, err error) { - line := 0 - column := 0 - if lineCol != "" { - lineColumn := strings.Split(lineCol, ":") - if line, err = strconv.Atoi(lineColumn[0]); err != nil { - return - } - if column, err = strconv.Atoi(lineColumn[1]); err != nil { - return - } - } - location = sarif.NewLocationWithPhysicalLocation( - sarif.NewPhysicalLocation(). - WithArtifactLocation( - sarif.NewSimpleArtifactLocation(file), - ).WithRegion( - sarif.NewSimpleRegion(line, line). - WithStartColumn(column)), - ) - return -} - -func getCodeFlowProperties(properties *sarifProperties) (flows []*sarif.CodeFlow, err error) { - for _, codeFlow := range properties.CodeFlows { - if len(codeFlow) == 0 { - continue - } - converted := sarif.NewCodeFlow() - locations := []*sarif.ThreadFlowLocation{} - for _, location := range codeFlow { - var convertedLocation *sarif.Location - if convertedLocation, err = getSarifLocation(location.File, location.LineColumn); err != nil { - return - } - locations = append(locations, sarif.NewThreadFlowLocation().WithLocation(convertedLocation)) - } - - converted.AddThreadFlow(sarif.NewThreadFlow().WithLocations(locations)) - flows = append(flows, converted) - } - return -} - -func generateSarifRuleID(properties *sarifProperties) string { - switch { - case properties.Cves != "": - return properties.Cves - case properties.XrayID != "": - return properties.XrayID - default: - return properties.File - } -} - func findMaxCVEScore(cves []formats.CveRow) (string, error) { maxCve := 0.0 for _, cve := range cves { diff --git a/xray/utils/resultwriter_test.go b/xray/utils/resultwriter_test.go index 1e4768e67..57458ea34 100644 --- a/xray/utils/resultwriter_test.go +++ b/xray/utils/resultwriter_test.go @@ -1,267 +1,24 @@ package utils import ( - "fmt" - "path" "testing" - "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" "github.com/jfrog/jfrog-cli-core/v2/xray/formats" - "github.com/jfrog/jfrog-client-go/xray/services" "github.com/stretchr/testify/assert" ) -func TestGenerateSarifFileFromScan(t *testing.T) { - extendedResults := &ExtendedScanResults{ - XrayResults: []services.ScanResponse{ - { - Vulnerabilities: []services.Vulnerability{ - { - Cves: []services.Cve{{Id: "CVE-2022-1234", CvssV3Score: "8.0"}, {Id: "CVE-2023-1234", CvssV3Score: "7.1"}}, - Summary: "A test vulnerability the harms nothing", - Severity: "High", - Components: map[string]services.Component{ - "vulnerability1": {FixedVersions: []string{"1.2.3"}}, - }, - Technology: coreutils.Go.ToString(), - }, - }, - }, - }, - SecretsScanResults: []SourceCodeScanResult{ - { - Severity: "Medium", - SourceCodeLocation: SourceCodeLocation{ - File: "found_secrets.js", - LineColumn: "1:18", - Text: "AAA************", - }, - Type: "entropy", - }, - }, - IacScanResults: []SourceCodeScanResult{ - { - Severity: "Medium", - SourceCodeLocation: SourceCodeLocation{ - File: "plan/nonapplicable/req_sw_terraform_azure_compute_no_pass_auth.json", - LineColumn: "229:38", - Text: "BBB************", - }, - Type: "entropy", - }, - }, - } - testCases := []struct { - name string - extendedResults *ExtendedScanResults - isMultipleRoots bool - markdownOutput bool - expectedSarifOutput string - }{ - { - name: "Scan results with vulnerabilities, secrets and IaC", - extendedResults: extendedResults, - expectedSarifOutput: "{\n \"version\": \"2.1.0\",\n \"$schema\": \"https://json.schemastore.org/sarif-2.1.0.json\",\n \"runs\": [\n {\n \"tool\": {\n \"driver\": {\n \"informationUri\": \"https://example.com/\",\n \"name\": \"JFrog Security\",\n \"rules\": [\n {\n \"id\": \"CVE-2022-1234, CVE-2023-1234\",\n \"shortDescription\": {\n \"text\": \"A test vulnerability the harms nothing\"\n },\n \"help\": {\n \"markdown\": \"\"\n },\n \"properties\": {\n \"security-severity\": \"8.0\"\n }\n },\n {\n \"id\": \"found_secrets.js\",\n \"shortDescription\": {\n \"text\": \"AAA************\"\n },\n \"help\": {\n \"markdown\": \"\"\n },\n \"properties\": {\n \"security-severity\": \"6.9\"\n }\n },\n {\n \"id\": \"plan/nonapplicable/req_sw_terraform_azure_compute_no_pass_auth.json\",\n \"shortDescription\": {\n \"text\": \"BBB************\"\n },\n \"help\": {\n \"markdown\": \"\"\n },\n \"properties\": {\n \"security-severity\": \"6.9\"\n }\n }\n ]\n }\n },\n \"results\": [\n {\n \"ruleId\": \"CVE-2022-1234, CVE-2023-1234\",\n \"ruleIndex\": 0,\n \"message\": {\n \"text\": \"[CVE-2022-1234, CVE-2023-1234] vulnerability1 \"\n },\n \"locations\": [\n {\n \"physicalLocation\": {\n \"artifactLocation\": {\n \"uri\": \"go.mod\"\n },\n \"region\": {\n \"startLine\": 0,\n \"startColumn\": 0,\n \"endLine\": 0\n }\n }\n }\n ]\n },\n {\n \"ruleId\": \"found_secrets.js\",\n \"ruleIndex\": 1,\n \"message\": {\n \"text\": \"Potential Secret Exposed\"\n },\n \"locations\": [\n {\n \"physicalLocation\": {\n \"artifactLocation\": {\n \"uri\": \"found_secrets.js\"\n },\n \"region\": {\n \"startLine\": 1,\n \"startColumn\": 18,\n \"endLine\": 1\n }\n }\n }\n ]\n },\n {\n \"ruleId\": \"plan/nonapplicable/req_sw_terraform_azure_compute_no_pass_auth.json\",\n \"ruleIndex\": 2,\n \"message\": {\n \"text\": \"Infrastructure as Code Vulnerability\"\n },\n \"locations\": [\n {\n \"physicalLocation\": {\n \"artifactLocation\": {\n \"uri\": \"plan/nonapplicable/req_sw_terraform_azure_compute_no_pass_auth.json\"\n },\n \"region\": {\n \"startLine\": 229,\n \"startColumn\": 38,\n \"endLine\": 229\n }\n }\n }\n ]\n }\n ]\n }\n ]\n}", - }, - { - name: "Scan results with vulnerabilities, secrets and IaC as Markdown", - extendedResults: extendedResults, - markdownOutput: true, - expectedSarifOutput: "{\n \"version\": \"2.1.0\",\n \"$schema\": \"https://json.schemastore.org/sarif-2.1.0.json\",\n \"runs\": [\n {\n \"tool\": {\n \"driver\": {\n \"informationUri\": \"https://example.com/\",\n \"name\": \"JFrog Security\",\n \"rules\": [\n {\n \"id\": \"CVE-2022-1234, CVE-2023-1234\",\n \"shortDescription\": {\n \"text\": \"\"\n },\n \"help\": {\n \"markdown\": \"| Severity Score | Direct Dependencies | Fixed Versions |\\n| :---: | :----: | :---: |\\n| 8.0 | | 1.2.3 |\\n\"\n },\n \"properties\": {\n \"security-severity\": \"8.0\"\n }\n },\n {\n \"id\": \"found_secrets.js\",\n \"shortDescription\": {\n \"text\": \"\"\n },\n \"help\": {\n \"markdown\": \"| Severity | File | Line:Column | Secret |\\n| :---: | :---: | :---: | :---: |\\n| Medium | found_secrets.js | 1:18 | AAA************ |\"\n },\n \"properties\": {\n \"security-severity\": \"6.9\"\n }\n },\n {\n \"id\": \"plan/nonapplicable/req_sw_terraform_azure_compute_no_pass_auth.json\",\n \"shortDescription\": {\n \"text\": \"\"\n },\n \"help\": {\n \"markdown\": \"| Severity | File | Line:Column | Finding |\\n| :---: | :---: | :---: | :---: |\\n| Medium | plan/nonapplicable/req_sw_terraform_azure_compute_no_pass_auth.json | 229:38 | BBB************ |\"\n },\n \"properties\": {\n \"security-severity\": \"6.9\"\n }\n }\n ]\n }\n },\n \"results\": [\n {\n \"ruleId\": \"CVE-2022-1234, CVE-2023-1234\",\n \"ruleIndex\": 0,\n \"message\": {\n \"text\": \"[CVE-2022-1234, CVE-2023-1234] vulnerability1 \"\n },\n \"locations\": [\n {\n \"physicalLocation\": {\n \"artifactLocation\": {\n \"uri\": \"go.mod\"\n },\n \"region\": {\n \"startLine\": 0,\n \"startColumn\": 0,\n \"endLine\": 0\n }\n }\n }\n ]\n },\n {\n \"ruleId\": \"found_secrets.js\",\n \"ruleIndex\": 1,\n \"message\": {\n \"text\": \"Potential Secret Exposed\"\n },\n \"locations\": [\n {\n \"physicalLocation\": {\n \"artifactLocation\": {\n \"uri\": \"found_secrets.js\"\n },\n \"region\": {\n \"startLine\": 1,\n \"startColumn\": 18,\n \"endLine\": 1\n }\n }\n }\n ]\n },\n {\n \"ruleId\": \"plan/nonapplicable/req_sw_terraform_azure_compute_no_pass_auth.json\",\n \"ruleIndex\": 2,\n \"message\": {\n \"text\": \"Infrastructure as Code Vulnerability\"\n },\n \"locations\": [\n {\n \"physicalLocation\": {\n \"artifactLocation\": {\n \"uri\": \"plan/nonapplicable/req_sw_terraform_azure_compute_no_pass_auth.json\"\n },\n \"region\": {\n \"startLine\": 229,\n \"startColumn\": 38,\n \"endLine\": 229\n }\n }\n }\n ]\n }\n ]\n }\n ]\n}", - }, - { - name: "Scan results without vulnerabilities", - extendedResults: &ExtendedScanResults{}, - isMultipleRoots: true, - markdownOutput: true, - expectedSarifOutput: "{\n \"version\": \"2.1.0\",\n \"$schema\": \"https://json.schemastore.org/sarif-2.1.0.json\",\n \"runs\": [\n {\n \"tool\": {\n \"driver\": {\n \"informationUri\": \"https://example.com/\",\n \"name\": \"JFrog Security\",\n \"rules\": []\n }\n },\n \"results\": []\n }\n ]\n}", - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - rw := NewResultsWriter(testCase.extendedResults). - SetIsMultipleRootProject(testCase.isMultipleRoots) - - sarifOutput, err := rw.generateSarifFileFromScan(testCase.markdownOutput, "JFrog Security", "https://example.com/") - assert.NoError(t, err) - assert.Equal(t, testCase.expectedSarifOutput, sarifOutput) - }) - } -} - func TestGetVulnerabilityOrViolationSarifHeadline(t *testing.T) { assert.Equal(t, "[CVE-2022-1234] loadsh 1.4.1", getVulnerabilityOrViolationSarifHeadline("loadsh", "1.4.1", "CVE-2022-1234")) assert.NotEqual(t, "[CVE-2022-1234] loadsh 1.4.1", getVulnerabilityOrViolationSarifHeadline("loadsh", "1.2.1", "CVE-2022-1234")) } -func TestGetCves(t *testing.T) { +func TestGetIssueIdentifier(t *testing.T) { issueId := "XRAY-123456" cvesRow := []formats.CveRow{{Id: "CVE-2022-1234"}} - assert.Equal(t, "CVE-2022-1234", getCves(cvesRow, issueId)) + assert.Equal(t, "CVE-2022-1234", GetIssueIdentifier(cvesRow, issueId)) cvesRow = append(cvesRow, formats.CveRow{Id: "CVE-2019-1234"}) - assert.Equal(t, "CVE-2022-1234, CVE-2019-1234", getCves(cvesRow, issueId)) - assert.Equal(t, issueId, getCves(nil, issueId)) -} - -func TestGetIacOrSecretsProperties(t *testing.T) { - testCases := []struct { - name string - row formats.SourceCodeRow - markdownOutput bool - isSecret JasScanType - expectedOutput sarifProperties - }{ - { - name: "Infrastructure as Code vulnerability without markdown output", - row: formats.SourceCodeRow{ - Severity: "high", - SourceCodeLocationRow: formats.SourceCodeLocationRow{ - File: path.Join("path", "to", "file"), - LineColumn: "10:5", - Text: "Vulnerable code", - }, - Type: "Terraform", - }, - markdownOutput: false, - isSecret: IaC, - expectedOutput: sarifProperties{ - Applicable: "", - Cves: "", - Headline: "Infrastructure as Code Vulnerability", - Severity: "8.9", - Description: "Vulnerable code", - MarkdownDescription: "", - XrayID: "", - File: path.Join("path", "to", "file"), - LineColumn: "10:5", - Type: "Terraform", - }, - }, - { - name: "Potential secret exposed with markdown output", - row: formats.SourceCodeRow{ - Severity: "medium", - SourceCodeLocationRow: formats.SourceCodeLocationRow{ - File: path.Join("path", "to", "file"), - LineColumn: "5:3", - Text: "Potential secret", - }, - Type: "AWS Secret Manager", - }, - markdownOutput: true, - isSecret: Secrets, - expectedOutput: sarifProperties{ - Applicable: "", - Cves: "", - Headline: "Potential Secret Exposed", - Severity: "6.9", - Description: "Potential secret", - MarkdownDescription: fmt.Sprintf("| Severity | File | Line:Column | Secret |\n| :---: | :---: | :---: | :---: |\n| medium | %s | 5:3 | Potential secret |", path.Join("path", "to", "file")), - XrayID: "", - File: path.Join("path", "to", "file"), - LineColumn: "5:3", - Type: "AWS Secret Manager", - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - output := getSourceCodeProperties(testCase.row, testCase.markdownOutput, testCase.isSecret) - assert.Equal(t, testCase.expectedOutput.Applicable, output.Applicable) - assert.Equal(t, testCase.expectedOutput.Cves, output.Cves) - assert.Equal(t, testCase.expectedOutput.Headline, output.Headline) - assert.Equal(t, testCase.expectedOutput.Severity, output.Severity) - assert.Equal(t, testCase.expectedOutput.Description, output.Description) - assert.Equal(t, testCase.expectedOutput.MarkdownDescription, output.MarkdownDescription) - assert.Equal(t, testCase.expectedOutput.XrayID, output.XrayID) - assert.Equal(t, testCase.expectedOutput.File, output.File) - assert.Equal(t, testCase.expectedOutput.LineColumn, output.LineColumn) - assert.Equal(t, testCase.expectedOutput.Type, output.Type) - }) - } -} - -func TestGetViolatedDepsSarifProps(t *testing.T) { - testCases := []struct { - name string - vulnerability formats.VulnerabilityOrViolationRow - markdownOutput bool - expectedOutput sarifProperties - }{ - { - name: "Vulnerability with markdown output", - vulnerability: formats.VulnerabilityOrViolationRow{ - Summary: "Vulnerable dependency", - Severity: "high", - Applicable: string(Applicable), - ImpactedDependencyName: "example-package", - ImpactedDependencyVersion: "1.0.0", - ImpactedDependencyType: "npm", - FixedVersions: []string{"1.0.1", "1.0.2"}, - Components: []formats.ComponentRow{ - {Name: "example-package", Version: "1.0.0"}, - }, - Cves: []formats.CveRow{ - {Id: "CVE-2021-1234", CvssV3: "7.2"}, - {Id: "CVE-2021-5678", CvssV3: "7.2"}, - }, - IssueId: "XRAY-12345", - }, - markdownOutput: true, - expectedOutput: sarifProperties{ - Applicable: "Applicable", - Cves: "CVE-2021-1234, CVE-2021-5678", - Headline: "[CVE-2021-1234, CVE-2021-5678] example-package 1.0.0", - Severity: "7.2", - Description: "Vulnerable dependency", - MarkdownDescription: "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 7.2 | Applicable | `example-package 1.0.0` | 1.0.1, 1.0.2 |\n", - }, - }, - { - name: "Vulnerability without markdown output", - vulnerability: formats.VulnerabilityOrViolationRow{ - Summary: "Vulnerable dependency", - Severity: "high", - Applicable: string(Applicable), - ImpactedDependencyName: "example-package", - ImpactedDependencyVersion: "1.0.0", - ImpactedDependencyType: "npm", - FixedVersions: []string{"1.0.1", "1.0.2"}, - Components: []formats.ComponentRow{ - {Name: "example-package", Version: "1.0.0"}, - }, - Cves: []formats.CveRow{ - {Id: "CVE-2021-1234", CvssV3: "7.2"}, - {Id: "CVE-2021-5678", CvssV3: "7.2"}, - }, - IssueId: "XRAY-12345", - }, - expectedOutput: sarifProperties{ - Applicable: "Applicable", - Cves: "CVE-2021-1234, CVE-2021-5678", - Headline: "[CVE-2021-1234, CVE-2021-5678] example-package 1.0.0", - Severity: "7.2", - Description: "Vulnerable dependency", - MarkdownDescription: "", - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - output, err := getViolatedDepsSarifProps(tc.vulnerability, tc.markdownOutput) - assert.NoError(t, err) - assert.Equal(t, tc.expectedOutput.Cves, output.Cves) - assert.Equal(t, tc.expectedOutput.Severity, output.Severity) - assert.Equal(t, tc.expectedOutput.XrayID, output.XrayID) - assert.Equal(t, tc.expectedOutput.MarkdownDescription, output.MarkdownDescription) - assert.Equal(t, tc.expectedOutput.Applicable, output.Applicable) - assert.Equal(t, tc.expectedOutput.Description, output.Description) - assert.Equal(t, tc.expectedOutput.Headline, output.Headline) - }) - } + assert.Equal(t, "CVE-2022-1234, CVE-2019-1234", GetIssueIdentifier(cvesRow, issueId)) + assert.Equal(t, issueId, GetIssueIdentifier(nil, issueId)) } func TestGetDirectDependenciesFormatted(t *testing.T) { diff --git a/xray/utils/sarifutils.go b/xray/utils/sarifutils.go index 0fb9ef5d9..5b5d31ab2 100644 --- a/xray/utils/sarifutils.go +++ b/xray/utils/sarifutils.go @@ -1,96 +1,168 @@ package utils import ( + "fmt" + "path/filepath" "strconv" "strings" + "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/owenrumney/go-sarif/v2/sarif" ) -// If exists SourceCodeScanResult with the same location as the provided SarifResult, return it -func GetResultIfExists(result *sarif.Result, workingDir string, results []*SourceCodeScanResult) *SourceCodeScanResult { - file := ExtractRelativePath(GetResultFileName(result), workingDir) - lineCol := GetResultLocationInFile(result) - text := *result.Message.Text - for _, result := range results { - if result.File == file && result.LineColumn == lineCol && result.Text == text { - return result - } +type SarifLevel string + +const ( + errorLevel SarifLevel = "error" + warningLevel SarifLevel = "warning" + infoLevel SarifLevel = "info" + noteLevel SarifLevel = "note" + noneLevel SarifLevel = "none" + + SeverityDefaultValue = "Medium" + + applicabilityRuleIdPrefix = "applic_" +) + +var ( + // All other values (include default) mapped as 'Medium' severity + levelToSeverity = map[SarifLevel]string{ + errorLevel: "High", + noteLevel: "Low", + noneLevel: "Unknown", } - return nil -} -func ConvertSarifResultToSourceCodeScanResult(result *sarif.Result, workingDir string) *SourceCodeScanResult { - file := ExtractRelativePath(GetResultFileName(result), workingDir) - lineCol := GetResultLocationInFile(result) - text := *result.Message.Text + severityToLevel = map[string]SarifLevel{ + "critical": errorLevel, + "high": errorLevel, + "medium": warningLevel, + "low": noteLevel, + "unknown": noneLevel, + } +) - return &SourceCodeScanResult{ - Severity: GetResultSeverity(result), - SourceCodeLocation: SourceCodeLocation{ - File: file, - LineColumn: lineCol, - Text: text, - }, - Type: *result.RuleID, +func NewReport() (*sarif.Report, error) { + report, err := sarif.New(sarif.Version210) + if err != nil { + return nil, errorutils.CheckError(err) } + return report, nil } -func GetResultCodeFlows(result *sarif.Result, workingDir string) (flows []*[]SourceCodeLocation) { - if len(result.CodeFlows) == 0 { +func ReadScanRunsFromFile(fileName string) (sarifRuns []*sarif.Run, err error) { + report, err := sarif.Open(fileName) + if errorutils.CheckError(err) != nil { + err = fmt.Errorf("can't read valid Sarif run from " + fileName + ": " + err.Error()) return } - for _, codeFlow := range result.CodeFlows { - if codeFlow == nil || len(codeFlow.ThreadFlows) == 0 { + sarifRuns = report.Runs + return +} + +func AggregateMultipleRunsIntoSingle(runs []*sarif.Run, destination *sarif.Run) { + if len(runs) == 0 { + return + } + for _, run := range runs { + if run == nil || len(run.Results) == 0 { continue } - flows = append(flows, extractThreadFlows(codeFlow.ThreadFlows, workingDir)...) + for _, rule := range GetRunRules(run) { + if destination.Tool.Driver != nil { + destination.Tool.Driver.AddRule(rule) + } + } + for _, result := range run.Results { + destination.AddResult(result) + } + for _, invocation := range run.Invocations { + destination.AddInvocations(invocation) + } + } +} + +func GetLocationRelatedCodeFlowsFromResult(location *sarif.Location, result *sarif.Result) (codeFlows []*sarif.CodeFlow) { + for _, codeFlow := range result.CodeFlows { + for _, stackTrace := range codeFlow.ThreadFlows { + // The threadFlow is reverse stack trace. + // The last location is the location that it relates to. + if isSameLocation(location, stackTrace.Locations[len(stackTrace.Locations)-1].Location) { + codeFlows = append(codeFlows, codeFlow) + } + } } return } -func extractThreadFlows(threadFlows []*sarif.ThreadFlow, workingDir string) (flows []*[]SourceCodeLocation) { - for _, threadFlow := range threadFlows { - if threadFlow == nil || len(threadFlow.Locations) == 0 { - continue +func isSameLocation(location *sarif.Location, other *sarif.Location) bool { + if location == other { + return true + } + return GetLocationFileName(location) == GetLocationFileName(other) && + GetLocationSnippet(location) == GetLocationSnippet(other) && + GetLocationStartLine(location) == GetLocationStartLine(other) && + GetLocationStartColumn(location) == GetLocationStartColumn(other) && + GetLocationEndLine(location) == GetLocationEndLine(other) && + GetLocationEndColumn(location) == GetLocationEndColumn(other) +} + +func GetResultsLocationCount(runs ...*sarif.Run) (count int) { + for _, run := range runs { + for _, result := range run.Results { + count += len(result.Locations) } - flow := extractStackTraceLocations(threadFlow.Locations, workingDir) - if len(flow) > 0 { - flows = append(flows, &flow) + } + return +} + +func GetLevelResultsLocationCount(run *sarif.Run, level SarifLevel) (count int) { + for _, result := range run.Results { + if level == SarifLevel(*result.Level) { + count += len(result.Locations) } } return } -func extractStackTraceLocations(locations []*sarif.ThreadFlowLocation, workingDir string) (flow []SourceCodeLocation) { - for _, location := range locations { - if location == nil { - continue +func GetResultsByRuleId(run *sarif.Run, ruleId string) (results []*sarif.Result) { + for _, result := range run.Results { + if *result.RuleID == ruleId { + results = append(results, result) } - flow = append(flow, SourceCodeLocation{ - File: ExtractRelativePath(getResultFileName(location.Location), workingDir), - LineColumn: getResultLocationInFile(location.Location), - Text: GetResultLocationSnippet(location.Location), - }) } return } -func GetResultLocationSnippet(location *sarif.Location) string { - if location != nil && location.PhysicalLocation != nil && location.PhysicalLocation.Region != nil && location.PhysicalLocation.Region.Snippet != nil { - return *location.PhysicalLocation.Region.Snippet.Text +func GetResultMsgText(result *sarif.Result) string { + if result.Message.Text != nil { + return *result.Message.Text } return "" } -func GetResultFileName(result *sarif.Result) string { - if len(result.Locations) > 0 { - return getResultFileName(result.Locations[0]) +func GetLocationSnippet(location *sarif.Location) string { + snippet := GetLocationSnippetPointer(location) + if snippet == nil { + return "" + } + return *snippet +} + +func GetLocationSnippetPointer(location *sarif.Location) *string { + region := getLocationRegion(location) + if region != nil && region.Snippet != nil { + return region.Snippet.Text + } + return nil +} + +func SetLocationSnippet(location *sarif.Location, snippet string) { + if location != nil && location.PhysicalLocation != nil && location.PhysicalLocation.Region != nil && location.PhysicalLocation.Region.Snippet != nil { + location.PhysicalLocation.Region.Snippet.Text = &snippet } - return "" } -func getResultFileName(location *sarif.Location) string { +func GetLocationFileName(location *sarif.Location) string { filePath := location.PhysicalLocation.ArtifactLocation.URI if filePath != nil { return *filePath @@ -98,14 +170,65 @@ func getResultFileName(location *sarif.Location) string { return "" } -func GetResultLocationInFile(result *sarif.Result) string { - if len(result.Locations) > 0 { - return getResultLocationInFile(result.Locations[0]) +func GetRelativeLocationFileName(location *sarif.Location, invocations []*sarif.Invocation) string { + wd := "" + if len(invocations) > 0 { + wd = GetInvocationWorkingDirectory(invocations[0]) + } + GetLocationFileName(location) + filePath := GetLocationFileName(location) + if filePath != "" { + return ExtractRelativePath(filePath, wd) } return "" } -func getResultLocationInFile(location *sarif.Location) string { +func SetLocationFileName(location *sarif.Location, fileName string) { + if location != nil && location.PhysicalLocation != nil && location.PhysicalLocation.Region != nil && location.PhysicalLocation.Region.Snippet != nil { + location.PhysicalLocation.ArtifactLocation.URI = &fileName + } +} + +func getLocationRegion(location *sarif.Location) *sarif.Region { + if location != nil && location.PhysicalLocation != nil { + return location.PhysicalLocation.Region + } + return nil +} + +func GetLocationStartLine(location *sarif.Location) int { + region := getLocationRegion(location) + if region != nil && region.StartLine != nil { + return *region.StartLine + } + return 0 +} + +func GetLocationStartColumn(location *sarif.Location) int { + region := getLocationRegion(location) + if region != nil && region.StartColumn != nil { + return *region.StartColumn + } + return 0 +} + +func GetLocationEndLine(location *sarif.Location) int { + region := getLocationRegion(location) + if region != nil && region.EndLine != nil { + return *region.EndLine + } + return 0 +} + +func GetLocationEndColumn(location *sarif.Location) int { + region := getLocationRegion(location) + if region != nil && region.EndColumn != nil { + return *region.EndColumn + } + return 0 +} + +func GetStartLocationInFile(location *sarif.Location) string { startLine := location.PhysicalLocation.Region.StartLine startColumn := location.PhysicalLocation.Region.StartColumn if startLine != nil && startColumn != nil { @@ -115,9 +238,14 @@ func getResultLocationInFile(location *sarif.Location) string { } func ExtractRelativePath(resultPath string, projectRoot string) string { - filePrefix := "file://" - relativePath := strings.ReplaceAll(strings.ReplaceAll(resultPath, projectRoot, ""), filePrefix, "") - return relativePath + // Remove OS-specific file prefix + resultPath = strings.TrimPrefix(resultPath, "file:///private") + resultPath = strings.TrimPrefix(resultPath, "file://") + + // Get relative path + relativePath := strings.ReplaceAll(resultPath, projectRoot, "") + trimSlash := strings.TrimPrefix(relativePath, string(filepath.Separator)) + return strings.TrimPrefix(trimSlash, "/") } func GetResultSeverity(result *sarif.Result) string { @@ -128,3 +256,43 @@ func GetResultSeverity(result *sarif.Result) string { } return SeverityDefaultValue } + +func ConvertToSarifLevel(severity string) string { + if level, ok := severityToLevel[strings.ToLower(severity)]; ok { + return string(level) + } + return string(noneLevel) +} + +func IsApplicableResult(result *sarif.Result) bool { + return !(result.Kind != nil && *result.Kind == "pass") +} + +func GetRuleFullDescription(rule *sarif.ReportingDescriptor) string { + if rule.FullDescription != nil && rule.FullDescription.Text != nil { + return *rule.FullDescription.Text + } + return "" +} + +func CveToApplicabilityRuleId(cveId string) string { + return applicabilityRuleIdPrefix + cveId +} + +func ApplicabilityRuleIdToCve(sarifRuleId string) string { + return strings.TrimPrefix(sarifRuleId, applicabilityRuleIdPrefix) +} + +func GetRunRules(run *sarif.Run) []*sarif.ReportingDescriptor { + if run != nil && run.Tool.Driver != nil { + return run.Tool.Driver.Rules + } + return []*sarif.ReportingDescriptor{} +} + +func GetInvocationWorkingDirectory(invocation *sarif.Invocation) string { + if invocation.WorkingDirectory != nil && invocation.WorkingDirectory.URI != nil { + return *invocation.WorkingDirectory.URI + } + return "" +} diff --git a/xray/utils/sarifutils_test.go b/xray/utils/sarifutils_test.go new file mode 100644 index 000000000..4e0031268 --- /dev/null +++ b/xray/utils/sarifutils_test.go @@ -0,0 +1,43 @@ +package utils + +import ( + "github.com/jfrog/gofrog/datastructures" + "github.com/owenrumney/go-sarif/v2/sarif" +) + +func getRunWithDummyResults(results ...*sarif.Result) *sarif.Run { + run := sarif.NewRunWithInformationURI("", "") + ids := datastructures.MakeSet[string]() + for _, result := range results { + if !ids.Exists(*result.RuleID) { + run.Tool.Driver.Rules = append(run.Tool.Driver.Rules, sarif.NewRule(*result.RuleID)) + ids.Add(*result.RuleID) + } + } + return run.WithResults(results) +} + +func getDummyPassingResult(ruleId string) *sarif.Result { + kind := "pass" + return &sarif.Result{ + Kind: &kind, + RuleID: &ruleId, + } +} + +func getDummyResultWithOneLocation(fileName string, startLine, startCol int, snippet, ruleId string, level string) *sarif.Result { + return &sarif.Result{ + Locations: []*sarif.Location{ + { + PhysicalLocation: &sarif.PhysicalLocation{ + ArtifactLocation: &sarif.ArtifactLocation{URI: &fileName}, + Region: &sarif.Region{ + StartLine: &startLine, + StartColumn: &startCol, + Snippet: &sarif.ArtifactContent{Text: &snippet}}}, + }, + }, + Level: &level, + RuleID: &ruleId, + } +}