Skip to content

Commit

Permalink
curation go support (#67)
Browse files Browse the repository at this point in the history
  • Loading branch information
asafambar authored Jun 10, 2024
1 parent e233194 commit 4170f2e
Show file tree
Hide file tree
Showing 23 changed files with 353 additions and 74 deletions.
10 changes: 7 additions & 3 deletions commands/audit/sca/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (

var DefaultExcludePatterns = []string{"*.git*", "*node_modules*", "*target*", "*venv*", "*test*"}

var curationErrorMsgToUserTemplate = "Failed to retrieve the dependencies tree for the %s project. Please contact your " +
var CurationErrorMsgToUserTemplate = "Failed to retrieve the dependencies tree for the %s project. Please contact your " +
"Artifactory administrator to verify pass-through for Curation audit is enabled for your project"

func GetExcludePattern(params utils.AuditParams) string {
Expand Down Expand Up @@ -180,11 +180,15 @@ func SuspectCurationBlockedError(isCurationCmd bool, tech techutils.Technology,
case techutils.Maven:
if strings.Contains(cmdOutput, "status code: 403") || strings.Contains(strings.ToLower(cmdOutput), "403 forbidden") ||
strings.Contains(cmdOutput, "status code: 500") {
msgToUser = fmt.Sprintf(curationErrorMsgToUserTemplate, techutils.Maven)
msgToUser = fmt.Sprintf(CurationErrorMsgToUserTemplate, techutils.Maven)
}
case techutils.Pip:
if strings.Contains(strings.ToLower(cmdOutput), "http error 403") {
msgToUser = fmt.Sprintf(curationErrorMsgToUserTemplate, techutils.Pip)
msgToUser = fmt.Sprintf(CurationErrorMsgToUserTemplate, techutils.Pip)
}
case techutils.Go:
if strings.Contains(strings.ToLower(cmdOutput), "403 forbidden") {
msgToUser = fmt.Sprintf(CurationErrorMsgToUserTemplate, techutils.Go)
}
}
return
Expand Down
20 changes: 14 additions & 6 deletions commands/audit/sca/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ func TestSuspectCurationBlockedError(t *testing.T) {
mvnOutput1 := "status code: 403, reason phrase: Forbidden (403)"
mvnOutput2 := "status code: 500, reason phrase: Server Error (500)"
pipOutput := "because of HTTP error 403 Client Error: Forbidden for url"
goOutput := "Failed running Go command: 403 Forbidden"

tests := []struct {
name string
Expand All @@ -293,21 +294,21 @@ func TestSuspectCurationBlockedError(t *testing.T) {
isCurationCmd: true,
tech: techutils.Maven,
output: mvnOutput1,
expect: fmt.Sprintf(curationErrorMsgToUserTemplate, techutils.Maven),
expect: fmt.Sprintf(CurationErrorMsgToUserTemplate, techutils.Maven),
},
{
name: "mvn 500 error",
isCurationCmd: true,
tech: techutils.Maven,
output: mvnOutput2,
expect: fmt.Sprintf(curationErrorMsgToUserTemplate, techutils.Maven),
expect: fmt.Sprintf(CurationErrorMsgToUserTemplate, techutils.Maven),
},
{
name: "pip 403 error",
isCurationCmd: true,
tech: techutils.Maven,
tech: techutils.Pip,
output: pipOutput,
expect: fmt.Sprintf(curationErrorMsgToUserTemplate, techutils.Pip),
expect: fmt.Sprintf(CurationErrorMsgToUserTemplate, techutils.Pip),
},
{
name: "pip not pass through error",
Expand All @@ -322,15 +323,22 @@ func TestSuspectCurationBlockedError(t *testing.T) {
output: "http error 401",
},
{
name: "nota supported tech",
name: "golang 403 error",
isCurationCmd: true,
tech: techutils.Go,
output: goOutput,
expect: fmt.Sprintf(CurationErrorMsgToUserTemplate, techutils.Go),
},
{
name: "not a supported tech",
isCurationCmd: true,
tech: coreutils.CI,
output: pipOutput,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
SuspectCurationBlockedError(tt.isCurationCmd, tt.tech, tt.output)
assert.Equal(t, SuspectCurationBlockedError(tt.isCurationCmd, tt.tech, tt.output), tt.expect)
})
}
}
54 changes: 47 additions & 7 deletions commands/audit/sca/go/golang.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package _go

import (
"errors"
"fmt"
biutils "github.com/jfrog/build-info-go/utils"
"github.com/jfrog/gofrog/datastructures"
goartifactoryutils "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/golang"
"github.com/jfrog/jfrog-cli-core/v2/utils/coreutils"
goutils "github.com/jfrog/jfrog-cli-core/v2/utils/golang"
"github.com/jfrog/jfrog-cli-security/commands/audit/sca"
"github.com/jfrog/jfrog-cli-security/utils"
"github.com/jfrog/jfrog-cli-security/utils/techutils"
xrayUtils "github.com/jfrog/jfrog-client-go/xray/services/utils"
"strings"
)
Expand All @@ -28,20 +31,36 @@ func BuildDependencyTree(params utils.AuditParams) (dependencyTree []*xrayUtils.
err = fmt.Errorf("failed while getting server details: %s", err.Error())
return
}
goProxyParams := goutils.GoProxyUrlParams{Direct: true}
// in case of curation command, we set an alternative cache folder when building go dep tree,
// also, it's not using the "direct" option, artifacts should be resolved only from the configured repo.
if params.IsCurationCmd() {
goProxyParams.EndpointPrefix = coreutils.CurationPassThroughApi
goProxyParams.Direct = false
projCacheDir, errCacheFolder := utils.GetCurationCacheFolderByTech(techutils.Go)
if errCacheFolder != nil {
err = errCacheFolder
return
}
if err = goartifactoryutils.SetGoModCache(projCacheDir); err != nil {
return
}
}

remoteGoRepo := params.DepsRepo()
if remoteGoRepo != "" {
if err = goartifactoryutils.SetArtifactoryAsResolutionServer(server, remoteGoRepo); err != nil {
if err = goartifactoryutils.SetArtifactoryAsResolutionServer(server, remoteGoRepo, goProxyParams); err != nil {
return
}
}

// Calculate go dependencies graph
dependenciesGraph, err := goutils.GetDependenciesGraph(currentDir)
dependenciesGraph, err := utils.GetDependenciesGraph(currentDir)
if err != nil || len(dependenciesGraph) == 0 {
return
}
// Calculate go dependencies list
dependenciesList, err := goutils.GetDependenciesList(currentDir)
dependenciesList, err := utils.GetDependenciesList(currentDir, handleCurationGoError)
if err != nil {
return
}
Expand All @@ -58,16 +77,37 @@ func BuildDependencyTree(params utils.AuditParams) (dependencyTree []*xrayUtils.
uniqueDepsSet := datastructures.MakeSet[string]()
populateGoDependencyTree(rootNode, dependenciesGraph, dependenciesList, uniqueDepsSet)

// In case of curation command, go version is not relevant as it can't be resolved from go repo
if !params.IsCurationCmd() {
if gotErr := addGoVersionToTree(rootNode, uniqueDepsSet); gotErr != nil {
err = gotErr
return
}
}

dependencyTree = []*xrayUtils.GraphNode{rootNode}
uniqueDeps = uniqueDepsSet.ToSlice()
return
}

func addGoVersionToTree(rootNode *xrayUtils.GraphNode, uniqueDepsSet *datastructures.Set[string]) error {
goVersionDependency, err := getGoVersionAsDependency()
if err != nil {
return
return err
}
rootNode.Nodes = append(rootNode.Nodes, goVersionDependency)
uniqueDepsSet.Add(goVersionDependency.Id)
return err
}

dependencyTree = []*xrayUtils.GraphNode{rootNode}
uniqueDeps = uniqueDepsSet.ToSlice()
return
func handleCurationGoError(err error) (bool, error) {
if err == nil {
return false, nil
}
if msgToUser := sca.SuspectCurationBlockedError(true, techutils.Go, err.Error()); msgToUser != "" {
return true, errors.New(msgToUser)
}
return false, nil
}

func populateGoDependencyTree(currNode *xrayUtils.GraphNode, dependenciesGraph map[string][]string, dependenciesList map[string]bool, uniqueDepsSet *datastructures.Set[string]) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package _go

import (
"errors"
"fmt"
"github.com/jfrog/jfrog-cli-security/utils/techutils"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -77,3 +80,32 @@ func removeTxtSuffix(txtFileName string) error {
// go.sum.txt >> go.sum
return fileutils.MoveFile(txtFileName, strings.TrimSuffix(txtFileName, ".txt"))
}

func Test_handleCurationGoError(t *testing.T) {

tests := []struct {
name string
err error
expectedError error
}{
{
name: "curation error 403",
err: errors.New("package download failed due to 403 forbidden test failure"),
expectedError: fmt.Errorf(sca.CurationErrorMsgToUserTemplate, techutils.Go),
},
{
name: "not curation error 500",
err: errors.New("package download failed due to 500 internal server error test failure"),
},
{
name: "no error",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := handleCurationGoError(tt.err)
assert.Equal(t, tt.expectedError, err)
assert.Equal(t, tt.expectedError != nil, got)
})
}
}
4 changes: 2 additions & 2 deletions commands/audit/scarunner.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,8 @@ func getDirectDependenciesFromTree(dependencyTrees []*xrayCmdUtils.GraphNode) []
}

func getCurationCacheByTech(tech techutils.Technology) (string, error) {
if tech == techutils.Maven {
return xrayutils.GetCurationMavenCacheFolder()
if tech == techutils.Maven || tech == techutils.Go {
return xrayutils.GetCurationCacheFolderByTech(tech)
}
return "", nil
}
Expand Down
41 changes: 32 additions & 9 deletions commands/curation/curationaudit.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const (
TotalConcurrentRequests = 10

MinArtiPassThroughSupport = "7.82.0"
MinArtiGolangSupport = "7.87.0"
MinXrayPassTHroughSupport = "3.92.0"
)

Expand All @@ -62,20 +63,23 @@ var CurationOutputFormats = []string{string(outFormat.Table), string(outFormat.J
var supportedTech = map[techutils.Technology]func(ca *CurationAuditCommand) (bool, error){
techutils.Npm: func(ca *CurationAuditCommand) (bool, error) { return true, nil },
techutils.Pip: func(ca *CurationAuditCommand) (bool, error) {
return ca.checkSupportByVersionOrEnv(techutils.Pip, utils.CurationPipSupport)
return ca.checkSupportByVersionOrEnv(techutils.Pip, utils.CurationSupportFlag, MinArtiPassThroughSupport)
},
techutils.Maven: func(ca *CurationAuditCommand) (bool, error) {
return ca.checkSupportByVersionOrEnv(techutils.Maven, utils.CurationMavenSupport)
return ca.checkSupportByVersionOrEnv(techutils.Maven, utils.CurationSupportFlag, MinArtiPassThroughSupport)
},
techutils.Go: func(ca *CurationAuditCommand) (bool, error) {
return ca.checkSupportByVersionOrEnv(techutils.Go, utils.CurationSupportFlag, MinArtiGolangSupport)
},
}

func (ca *CurationAuditCommand) checkSupportByVersionOrEnv(tech techutils.Technology, envName string) (bool, error) {
func (ca *CurationAuditCommand) checkSupportByVersionOrEnv(tech techutils.Technology, envName string, minArtiVersion string) (bool, error) {
if flag, err := clientutils.GetBoolEnvValue(envName, false); flag {
return true, nil
} else if err != nil {
log.Error(err)
}
rtVersion, serverDetails, err := ca.getRtVersionAndServiceDetails(tech)
artiVersion, serverDetails, err := ca.getRtVersionAndServiceDetails(tech)
if err != nil {
return false, err
}
Expand All @@ -86,7 +90,7 @@ func (ca *CurationAuditCommand) checkSupportByVersionOrEnv(tech techutils.Techno
}

xrayVersionErr := clientutils.ValidateMinimumVersion(clientutils.Xray, xrayVersion, MinXrayPassTHroughSupport)
rtVersionErr := clientutils.ValidateMinimumVersion(clientutils.Artifactory, rtVersion, MinArtiPassThroughSupport)
rtVersionErr := clientutils.ValidateMinimumVersion(clientutils.Artifactory, artiVersion, minArtiVersion)
if xrayVersionErr != nil || rtVersionErr != nil {
return false, errors.Join(xrayVersionErr, rtVersionErr)
}
Expand Down Expand Up @@ -304,6 +308,7 @@ func (ca *CurationAuditCommand) auditTree(tech techutils.Technology, results map
return err
}
rootNode := depTreeResult.FullDepTrees[0]
// we don't pass artiUrl and repo as we don't want to download the package, only to get the name and version.
_, projectName, projectScope, projectVersion := getUrlNameAndVersionByTech(tech, rootNode, nil, "", "")
if projectName == "" {
workPath, err := os.Getwd()
Expand All @@ -312,9 +317,12 @@ func (ca *CurationAuditCommand) auditTree(tech techutils.Technology, results map
}
projectName = filepath.Base(workPath)
}

fullProjectName := projectName
if projectVersion != "" {
fullProjectName += ":" + projectVersion
}
if ca.Progress() != nil {
ca.Progress().SetHeadlineMsg(fmt.Sprintf("Fetch curation status for %s graph with %v nodes project name: %s:%s", tech.ToFormal(), len(depTreeResult.FlatTree.Nodes)-1, projectName, projectVersion))
ca.Progress().SetHeadlineMsg(fmt.Sprintf("Fetch curation status for %s graph with %v nodes project name: %s", tech.ToFormal(), len(depTreeResult.FlatTree.Nodes)-1, fullProjectName))
}
if projectScope != "" {
projectName = projectScope + "/" + projectName
Expand Down Expand Up @@ -626,7 +634,8 @@ func getUrlNameAndVersionByTech(tech techutils.Technology, node *xrayUtils.Graph
case techutils.Pip:
downloadUrls, name, version = getPythonNameVersion(node.Id, downloadUrlsMap)
return

case techutils.Go:
return getGoNameScopeAndVersion(node.Id, artiUrl, repo)
}
return
}
Expand All @@ -648,7 +657,21 @@ func getPythonNameVersion(id string, downloadUrlsMap map[string]string) (downloa
return
}

// input- id: gav://org.apache.tomcat.embed:tomcat-embed-jasper:8.0.33
// input - id: go://github.com/kennygrant/sanitize:v1.2.4
// input - repo: go
// output: downloadUrl: <artiUrl>/api/go/go/github.com/kennygrant/sanitize/@v/v1.2.4.zip
func getGoNameScopeAndVersion(id, artiUrl, repo string) (downloadUrls []string, name, scope, version string) {
id = strings.TrimPrefix(id, techutils.Go.String()+"://")
nameVersion := strings.Split(id, ":")
name = nameVersion[0]
if len(nameVersion) > 1 {
version = nameVersion[1]
}
url := strings.TrimSuffix(artiUrl, "/") + "/api/go/" + repo + "/" + name + "/@v/" + version + ".zip"
return []string{url}, name, "", version
}

// input(with classifier) - id: gav://org.apache.tomcat.embed:tomcat-embed-jasper:8.0.33-jdk15
// input - repo: libs-release
// output - downloadUrl: <arti-url>/libs-release/org/apache/tomcat/embed/tomcat-embed-jasper/8.0.33/tomcat-embed-jasper-8.0.33-jdk15.jar
func getMavenNameScopeAndVersion(id, artiUrl, repo string, node *xrayUtils.GraphNode) (downloadUrls []string, name, scope, version string) {
Expand Down
Loading

0 comments on commit 4170f2e

Please sign in to comment.