From df9cf697ec199042408a82a1ef7de126d63bf214 Mon Sep 17 00:00:00 2001 From: luotianqi777 Date: Fri, 3 Mar 2023 10:05:22 +0800 Subject: [PATCH 1/2] support cyclonedx format report --- cli/main.go | 21 +++++++++++--- util/go.mod | 6 +++- util/go.sum | 3 ++ util/model/purl.go | 31 ++++++++++++++++++++ util/report/cyclonedx.go | 62 ++++++++++++++++++++++++++++++++++++++++ util/report/format.go | 11 +++++++ 6 files changed, 129 insertions(+), 5 deletions(-) create mode 100644 util/model/purl.go create mode 100644 util/report/cyclonedx.go diff --git a/cli/main.go b/cli/main.go index 95786ba..11f31c7 100644 --- a/cli/main.go +++ b/cli/main.go @@ -8,6 +8,7 @@ import ( "analyzer/engine" "flag" "fmt" + "io" "path" "strings" "util/args" @@ -34,6 +35,7 @@ func output(depRoot *model.DepTree, taskInfo report.TaskInfo) { logs.Debug("\n" + depRoot.String()) // 输出结果 var reportFunc func(*model.DepTree, report.TaskInfo) []byte + var reportByWriterFunc func(io.Writer, *model.DepTree, report.TaskInfo) out := args.Config.Out switch path.Ext(out) { case ".html": @@ -41,22 +43,33 @@ func output(depRoot *model.DepTree, taskInfo report.TaskInfo) { case ".json": if strings.HasSuffix(out, ".spdx.json") { reportFunc = report.SpdxJson - break + } else if strings.HasSuffix(out, ".cdx.json") { + reportByWriterFunc = report.CycloneDXJson + } else { + reportFunc = report.Json } - reportFunc = report.Json case ".spdx": reportFunc = report.Spdx case ".xml": if strings.HasSuffix(out, ".spdx.xml") { reportFunc = report.SpdxXml - break + } else if strings.HasSuffix(out, ".cdx.xml") { + reportByWriterFunc = report.CycloneDXXml + } else { + logs.Warn(fmt.Sprintf("not support report format: %s", args.Config.Out)) } default: reportFunc = report.Json } fmt.Println(report.Statis(depRoot, taskInfo)) if args.Config.Out != "" { - report.Save(reportFunc(depRoot, taskInfo), args.Config.Out) + if reportFunc != nil { + report.Save(reportFunc(depRoot, taskInfo), args.Config.Out) + } else if reportByWriterFunc != nil { + report.SaveByWriter(func(w io.Writer) { + reportByWriterFunc(w, depRoot, taskInfo) + }, args.Config.Out) + } } else { fmt.Println(string(reportFunc(depRoot, taskInfo))) } diff --git a/util/go.mod b/util/go.mod index e663c04..e2ad6a7 100644 --- a/util/go.mod +++ b/util/go.mod @@ -2,4 +2,8 @@ module util go 1.18 -require github.com/pkg/errors v0.9.1 +require ( + github.com/CycloneDX/cyclonedx-go v0.7.0 + github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 + github.com/pkg/errors v0.9.1 +) diff --git a/util/go.sum b/util/go.sum index 7c401c3..6a02893 100644 --- a/util/go.sum +++ b/util/go.sum @@ -1,2 +1,5 @@ +github.com/CycloneDX/cyclonedx-go v0.7.0 h1:jNxp8hL7UpcvPDFXjY+Y1ibFtsW+e5zyF9QoSmhK/zg= +github.com/CycloneDX/cyclonedx-go v0.7.0/go.mod h1:W5Z9w8pTTL+t+yG3PCiFRGlr8PUlE0pGWzKSJbsyXkg= +github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394/go.mod h1:Q8n74mJTIgjX4RBBcHnJ05h//6/k6foqmgE45jTQtxg= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/util/model/purl.go b/util/model/purl.go new file mode 100644 index 0000000..62ee493 --- /dev/null +++ b/util/model/purl.go @@ -0,0 +1,31 @@ +package model + +import ( + "fmt" + "util/enum/language" +) + +var purlMap = map[language.Type]string{ + language.Rust: "cargo", + language.Php: "composer", + language.Ruby: "gem", + language.Golang: "golang", + language.Java: "maven", + language.JavaScript: "npm", + language.Python: "pypi", +} + +func (dep DepTree) Purl() string { + group := "" + if g, ok := purlMap[dep.Language]; ok { + group = g + } + version := dep.VersionStr + if dep.Version != nil && dep.Version.Org != "" { + version = dep.Version.Org + } + if dep.Vendor == "" { + return fmt.Sprintf("pkg:%s/%s@%s", group, dep.Name, version) + } + return fmt.Sprintf("pkg:%s/%s/%s@%s", group, dep.Vendor, dep.Name, version) +} diff --git a/util/report/cyclonedx.go b/util/report/cyclonedx.go new file mode 100644 index 0000000..151a497 --- /dev/null +++ b/util/report/cyclonedx.go @@ -0,0 +1,62 @@ +package report + +import ( + "io" + "strings" + "util/model" + + "github.com/CycloneDX/cyclonedx-go" +) + +func buildCycBom(dep *model.DepTree, taskInfo TaskInfo) *cyclonedx.BOM { + format(dep) + metadata := cyclonedx.Metadata{} + components := []cyclonedx.Component{} + dependencies := []cyclonedx.Dependency{} + q := []*model.DepTree{dep} + for len(q) > 0 { + n := q[0] + q = append(q[1:], n.Children...) + if n == dep { + metadata.Component = &cyclonedx.Component{ + BOMRef: n.Purl(), + Type: cyclonedx.ComponentTypeApplication, + Name: n.Name, + Version: n.VersionStr, + } + continue + } + if n.Name != "" { + components = append(components, cyclonedx.Component{ + BOMRef: n.Purl(), + Type: cyclonedx.ComponentTypeLibrary, + Author: n.Vendor, + Name: n.Name[strings.LastIndex(n.Name, "/")+1:], + Version: n.VersionStr, + }) + var deps []string + for _, child := range n.Children { + deps = append(deps, child.Purl()) + } + dependencies = append(dependencies, cyclonedx.Dependency{ + Ref: n.Purl(), + Dependencies: &deps, + }) + } + } + bom := cyclonedx.NewBOM() + bom.Metadata = &metadata + bom.Components = &components + bom.Dependencies = &dependencies + return bom +} + +func CycloneDXJson(writer io.Writer, dep *model.DepTree, taskInfo TaskInfo) { + bom := buildCycBom(dep, taskInfo) + cyclonedx.NewBOMEncoder(writer, cyclonedx.BOMFileFormatJSON).SetPretty(true).Encode(bom) +} + +func CycloneDXXml(writer io.Writer, dep *model.DepTree, taskInfo TaskInfo) { + bom := buildCycBom(dep, taskInfo) + cyclonedx.NewBOMEncoder(writer, cyclonedx.BOMFileFormatXML).SetPretty(true).Encode(bom) +} diff --git a/util/report/format.go b/util/report/format.go index b23d439..f7f70f2 100644 --- a/util/report/format.go +++ b/util/report/format.go @@ -2,6 +2,7 @@ package report import ( "fmt" + "io" "os" "util/args" "util/enum/language" @@ -90,3 +91,13 @@ func Save(data []byte, filepath string) { } } } + +// Save 保存结果文件 +func SaveByWriter(wf func(io.Writer), filepath string) { + if f, err := os.Create(filepath); err != nil { + logs.Error(err) + } else { + defer f.Close() + wf(f) + } +} -- Gitee From a22d8332f6486a3e78482651a6c378ef97c709dd Mon Sep 17 00:00:00 2001 From: luotianqi777 Date: Mon, 6 Mar 2023 19:54:17 +0800 Subject: [PATCH 2/2] fix purl no language --- util/model/purl.go | 22 +++++++++++++++++++--- util/report/cyclonedx.go | 20 +++++++++++--------- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/util/model/purl.go b/util/model/purl.go index 62ee493..3335b2c 100644 --- a/util/model/purl.go +++ b/util/model/purl.go @@ -15,10 +15,26 @@ var purlMap = map[language.Type]string{ language.Python: "pypi", } -func (dep DepTree) Purl() string { +var purlStrMap = map[string]string{ + language.Rust.String(): "cargo", + language.Php.String(): "composer", + language.Ruby.String(): "gem", + language.Golang.String(): "golang", + language.Java.String(): "maven", + language.JavaScript.String(): "npm", + language.Python.String(): "pypi", +} + +func (dep Dependency) Purl() string { group := "" - if g, ok := purlMap[dep.Language]; ok { - group = g + if dep.Language == language.None { + if g, ok := purlStrMap[dep.LanguageStr]; ok { + group = g + } + } else { + if g, ok := purlMap[dep.Language]; ok { + group = g + } } version := dep.VersionStr if dep.Version != nil && dep.Version.Org != "" { diff --git a/util/report/cyclonedx.go b/util/report/cyclonedx.go index 151a497..ef6a91c 100644 --- a/util/report/cyclonedx.go +++ b/util/report/cyclonedx.go @@ -19,20 +19,22 @@ func buildCycBom(dep *model.DepTree, taskInfo TaskInfo) *cyclonedx.BOM { q = append(q[1:], n.Children...) if n == dep { metadata.Component = &cyclonedx.Component{ - BOMRef: n.Purl(), - Type: cyclonedx.ComponentTypeApplication, - Name: n.Name, - Version: n.VersionStr, + BOMRef: n.Purl(), + Type: cyclonedx.ComponentTypeApplication, + Name: n.Name, + Version: n.VersionStr, + PackageURL: n.Purl(), } continue } if n.Name != "" { components = append(components, cyclonedx.Component{ - BOMRef: n.Purl(), - Type: cyclonedx.ComponentTypeLibrary, - Author: n.Vendor, - Name: n.Name[strings.LastIndex(n.Name, "/")+1:], - Version: n.VersionStr, + BOMRef: n.Purl(), + Type: cyclonedx.ComponentTypeLibrary, + Author: n.Vendor, + Name: n.Name[strings.LastIndex(n.Name, "/")+1:], + Version: n.VersionStr, + PackageURL: n.Purl(), }) var deps []string for _, child := range n.Children { -- Gitee