Skip to content

Commit

Permalink
Merge branch 'dev' into token_validation
Browse files Browse the repository at this point in the history
  • Loading branch information
barv-jfrog authored Aug 11, 2024
2 parents 3b9bfdb + 998c5f1 commit 6ac03ab
Show file tree
Hide file tree
Showing 8 changed files with 1,417 additions and 12 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ jobs:
run: python -m pip install pipenv
- name: Setup Poetry
run: python -m pip install poetry
- name: Setup Conan
run: |
python -m pip install conan
conan profile detect
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
with:
Expand Down
150 changes: 150 additions & 0 deletions commands/audit/sca/conan/conan.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package conan

import (
"encoding/json"
"errors"
"fmt"
"os/exec"

"github.com/jfrog/gofrog/io"
"github.com/jfrog/gofrog/version"
"github.com/jfrog/jfrog-client-go/utils/errorutils"
xrayUtils "github.com/jfrog/jfrog-client-go/xray/services/utils"

"github.com/jfrog/jfrog-cli-core/v2/utils/coreutils"

"github.com/jfrog/jfrog-client-go/utils/log"

"github.com/jfrog/jfrog-cli-security/utils"
)

const (
PackageTypeIdentifier = "conan://"
conanV2 = "2.0.0"
)

func BuildDependencyTree(params utils.AuditParams) (dependencyTrees []*xrayUtils.GraphNode, uniqueDeps []string, err error) {
// Prepare
currentDir, err := coreutils.GetWorkingDirectory()
if err != nil {
return
}
conanExecPath, err := getConanExecPath()
if err != nil {
return
}
// Build
return calculateDependencies(conanExecPath, currentDir, params)
}

func getConanExecPath() (conanExecPath string, err error) {
if conanExecPath, err = exec.LookPath("conan"); errorutils.CheckError(err) != nil {
return
}
if conanExecPath == "" {
err = errors.New("could not find the 'conan' executable in the system PATH")
return
}
log.Debug("Using Conan executable:", conanExecPath)
// Validate conan version command
conanVersion, err := getConanCmd(conanExecPath, "", "--version").RunWithOutput()
if errorutils.CheckError(err) != nil {
return
}
if version.NewVersion(string(conanVersion)).Compare(conanV2) < 0 {
err = fmt.Errorf("Conan dependency tree building is currently supported for Conan V2. The current Conan version is: %s", conanVersion)
return
}
log.Debug("Conan version: ", string(conanVersion))
return
}

func getConanCmd(conanExecPath, workingDir, cmd string, args ...string) *io.Command {
command := io.NewCommand(conanExecPath, cmd, args)
command.Dir = workingDir
return command
}

type conanDep struct {
Ref string `json:"ref"`
Direct bool `json:"direct"`
}

type conanRef struct {
Ref string `json:"ref"`
Name string `json:"name"`
Version string `json:"version"`
Dependencies map[string]conanDep `json:"dependencies"`
node *xrayUtils.GraphNode
}

func (cr *conanRef) Node(children ...*xrayUtils.GraphNode) *xrayUtils.GraphNode {
if cr.node == nil {
cr.node = &xrayUtils.GraphNode{Id: cr.NodeName(), Nodes: children}
}
return cr.node
}

func (cr *conanRef) NodeName() string {
return PackageTypeIdentifier + cr.Name + ":" + cr.Version
}

type conanGraphOutput struct {
Graph struct {
Nodes map[string]conanRef `json:"nodes"`
} `json:"graph"`
}

func calculateDependencies(executablePath, workingDir string, params utils.AuditParams) (dependencyTrees []*xrayUtils.GraphNode, uniqueDeps []string, err error) {
graphInfo := append([]string{"info", ".", "--format=json"}, params.Args()...)
conanGraphInfoContent, err := getConanCmd(executablePath, workingDir, "graph", graphInfo...).RunWithOutput()
if err != nil {
return
}

log.Debug("Conan 'graph info' command output:\n", string(conanGraphInfoContent))
var output conanGraphOutput
if err = json.Unmarshal(conanGraphInfoContent, &output); err != nil {
return
}

rootNode, err := parseConanDependencyGraph("0", output.Graph.Nodes)
if err != nil {
return
}
dependencyTrees = append(dependencyTrees, rootNode)

for id, dep := range output.Graph.Nodes {
if id == "0" {
continue
}
uniqueDeps = append(uniqueDeps, dep.NodeName())
}

return
}

func parseConanDependencyGraph(id string, graph map[string]conanRef) (*xrayUtils.GraphNode, error) {
var childrenNodes []*xrayUtils.GraphNode
node, ok := graph[id]
if !ok {
return nil, fmt.Errorf("got non-existent node id %s", id)
}
for key, dep := range node.Dependencies {
// Conan includes some transitive dependencies here. To keep it consistent with other package managers,
// we'll exclude the indirect dependencies from the tree.
if !dep.Direct {
continue
}
parsedNode, err := parseConanDependencyGraph(key, graph)
if err != nil {
return nil, err
}
childrenNodes = append(childrenNodes, parsedNode)
}
if id == "0" {
return &xrayUtils.GraphNode{Id: "root", Nodes: childrenNodes}, nil
} else {
return node.Node(childrenNodes...), nil
}
}
54 changes: 54 additions & 0 deletions commands/audit/sca/conan/conan_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package conan

import (
"encoding/json"
"os"
"path/filepath"
"testing"

xrayUtils "github.com/jfrog/jfrog-client-go/xray/services/utils"
"github.com/stretchr/testify/assert"

"github.com/jfrog/jfrog-cli-core/v2/utils/tests"
"github.com/jfrog/jfrog-cli-security/commands/audit/sca"
"github.com/jfrog/jfrog-cli-security/utils"
)

var expectedResult = &xrayUtils.GraphNode{
Id: "root",
Nodes: []*xrayUtils.GraphNode{
{Id: "conan://zlib:1.3.1"},
{Id: "conan://openssl:3.0.9", Nodes: []*xrayUtils.GraphNode{{Id: "conan://zlib:1.3.1"}}},
{Id: "conan://meson:1.4.1", Nodes: []*xrayUtils.GraphNode{{Id: "conan://ninja:1.11.1"}}},
},
}

func TestParseConanDependencyTree(t *testing.T) {
_, cleanUp := sca.CreateTestWorkspace(t, filepath.Join("other", "conan"))
defer cleanUp()
dependenciesJson, err := os.ReadFile("dependencies.json")
assert.NoError(t, err)

var output conanGraphOutput
err = json.Unmarshal(dependenciesJson, &output)
assert.NoError(t, err)

graph, err := parseConanDependencyGraph("0", output.Graph.Nodes)
assert.NoError(t, err)
if !tests.CompareTree(expectedResult, graph) {
t.Errorf("expected %+v, got: %+v", expectedResult.Nodes, graph)
}
}

func TestBuildDependencyTree(t *testing.T) {
_, cleanUp := sca.CreateTestWorkspace(t, filepath.Join("projects", "package-managers", "conan"))
defer cleanUp()
expectedUniqueDeps := []string{"conan://openssl:3.0.9", "conan://zlib:1.3.1", "conan://meson:1.4.1", "conan://ninja:1.11.1"}
params := &utils.AuditBasicParams{}
graph, uniqueDeps, err := BuildDependencyTree(params)
assert.NoError(t, err)
if !tests.CompareTree(expectedResult, graph[0]) {
t.Errorf("expected %+v, got: %+v", expectedResult.Nodes, graph)
}
assert.ElementsMatch(t, uniqueDeps, expectedUniqueDeps, "First is actual, Second is Expected")
}
Loading

0 comments on commit 6ac03ab

Please sign in to comment.