Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

支持Dpsbom #307

Merged
merged 1 commit into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions cmd/format/dpsbom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package format

import (
"archive/zip"
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"hash"
"io"
"path/filepath"
"strings"

"github.com/xmirrorsecurity/opensca-cli/v3/cmd/detail"
"github.com/xmirrorsecurity/opensca-cli/v3/opensca/model"
)

func DpSbomZip(report Report, out string) {
zipFile := out
if !strings.HasSuffix(out, ".zip") {
zipFile = out + ".zip"
}
jsonName := filepath.Base(out)
if !strings.HasSuffix(jsonName, ".json") {
jsonName = jsonName + ".json"
}
outWrite(zipFile, func(w io.Writer) error {
doc := pdSbomDoc(report)
if doc.Hashes.HashFile == "" {
return errors.New("hash file is required")
}

var h hash.Hash
switch strings.ToLower(doc.Hashes.Algorithm) {
case "sha-256":
h = sha256.New()
case "sha-1":
h = sha1.New()
case "md5":
h = md5.New()
case "":
return errors.New("hash algorithm is required")
default:
return fmt.Errorf("unsupported hash algorithm: %s", doc.Hashes.Algorithm)
}

tojson := func(w io.Writer) error {
encoder := json.NewEncoder(w)
encoder.SetIndent("", " ")
return encoder.Encode(doc)
}

zipfile := zip.NewWriter(w)
defer zipfile.Close()

sbomfile, err := zipfile.Create(jsonName)
if err != nil {
return err
}
err = tojson(sbomfile)
if err != nil {
return err
}

hashfile, err := zipfile.Create(doc.Hashes.HashFile)
if err != nil {
return err
}
err = tojson(h)
if err != nil {
return err
}
hashstr := hex.EncodeToString(h.Sum(nil)[:])
hashfile.Write([]byte(hashstr))

return nil
})
}

func pdSbomDoc(report Report) *model.DpSbomDocument {

doc := model.NewDpSbomDocument(report.TaskInfo.AppName, "opensca-cli")

report.DepDetailGraph.ForEach(func(n *detail.DepDetailGraph) bool {

if n.Name == "" {
return true
}

lics := []string{}
for _, lic := range n.Licenses {
lics = append(lics, lic.ShortName)
}
doc.AppendComponents(func(dsp *model.DpSbomPackage) {
dsp.Identifier.Purl = n.Purl()
dsp.Name = n.Name
dsp.Version = n.Version
dsp.License = lics
})

children := []string{}
for _, c := range n.Children {
if c.Name == "" {
continue
}
children = append(children, c.Purl())
}
doc.AppendDependencies(n.Purl(), children)

return true
})

return doc
}
10 changes: 10 additions & 0 deletions cmd/format/save.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ func Save(report Report, output string) {
switch filepath.Ext(out) {
case ".html":
Html(genReport(report), out)
case ".zip":
if strings.HasSuffix(out, ".dpsbom.zip") {
DpSbomZip(report, out)
} else {
Json(genReport(report), out)
}
case ".json":
if strings.HasSuffix(out, ".spdx.json") {
SpdxJson(report, out)
Expand All @@ -48,9 +54,13 @@ func Save(report Report, output string) {
CycloneDXJson(report, out)
} else if strings.HasSuffix(out, ".swid.json") {
SwidJson(report, out)
} else if strings.HasSuffix(out, ".dpsbom.json") {
DpSbomZip(report, out)
} else {
Json(genReport(report), out)
}
case ".dpsbom":
DpSbomZip(report, out)
case ".dsdx":
Dsdx(report, out)
case ".spdx":
Expand Down
112 changes: 112 additions & 0 deletions opensca/model/dpsbom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package model

import "time"

type DpSbomDocument struct {
// 文档名称
DocumentName string `json:"DocumentName"`
// 文档版本
DocumentVersion string `json:"DocumentVersion"`
// 文档创建/更新时间 yyyy-MM-ddTHH:mm:ssTZD
DocumentTime string `json:"DocumentTime"`
// 文档格式
BomFormat string `json:"BomFormat"`
// 生成工具
Tool string `json:"tool"`
// sbom签名信息
Hashes DpSbomHashes `json:"Hashes"`
// 组件列表
Packages []DpSbomPackage `json:"Packages"`
// 依赖关系
Dependencies []DpSbomDependencies `json:"Dependencies"`
}

type DpSbomPackage struct {
Name string `json:"ComponentName"`
Version string `json:"ComponentVersion"`

Identifier struct {
Purl string `json:"PURL"`
} `json:"ComponentIdentifier"`

License []string `json:"License"`

Author []map[string]string `json:"Author"`
Provider []map[string]string `json:"Provider"`
Hash DpSbomHash `json:"ComponentHash"`

// 组件信息更新时间 yyyy-MM-ddTHH:mm:ssTZD
Timestamp string `json:"Timestamp"`
}

type DpSbomDependencies struct {
Ref string `json:"Ref"`
DependsOn []struct {
Target string `json:"Target"`
} `json:"DependsOn"`
}

func newDependencies(ref string, dependsOn []string) DpSbomDependencies {
deps := DpSbomDependencies{Ref: ref}
deps.DependsOn = make([]struct {
Target string "json:\"Target\""
}, len(dependsOn))
for i, d := range dependsOn {
deps.DependsOn[i].Target = d
}
return deps
}

type DpSbomHashes struct {
Algorithm string `json:"Algorithm"`
HashFile string `json:"HashFile,omitempty"`
DigitalFile string `json:"DigitalFile,omitempty"`
}

type DpSbomHash struct {
Algorithm string `json:"Algorithm,omitempty"`
Hash string `json:"Hash,omitempty"`
}

func NewDpSbomDocument(name, creator string) *DpSbomDocument {
version := "1.0.0"
timestamp := time.Now().Format("2006-01-02T15:04:05MST")
return &DpSbomDocument{
DocumentName: name,
DocumentVersion: version,
DocumentTime: timestamp,
BomFormat: "DP-SBOM-1.0",
Tool: creator,
Hashes: DpSbomHashes{
Algorithm: "SHA-256",
HashFile: "sha256.txt",
},
Dependencies: []DpSbomDependencies{},
}
}

func (doc *DpSbomDocument) AppendComponents(fn func(*DpSbomPackage)) {
c := DpSbomPackage{}
if fn != nil {
fn(&c)
}
if c.Timestamp == "" {
c.Timestamp = time.Now().Format("2006-01-02T15:04:05MST")
}
if c.Author == nil {
c.Author = []map[string]string{}
}
if c.Provider == nil {
c.Provider = []map[string]string{}
}
doc.Packages = append(doc.Packages, c)
}

func (doc *DpSbomDocument) AppendDependencies(parentId string, childrenIds []string) {
if doc.Dependencies == nil {
doc.Dependencies = []DpSbomDependencies{}
}
if len(childrenIds) > 0 {
doc.Dependencies = append(doc.Dependencies, newDependencies(parentId, childrenIds))
}
}
9 changes: 5 additions & 4 deletions opensca/sca/filter/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,11 @@ var (
)

var (
SbomSpdx = filterFunc(strings.HasSuffix, ".spdx")
SbomDsdx = filterFunc(strings.HasSuffix, ".dsdx")
SbomJson = filterFunc(strings.HasSuffix, ".json")
SbomXml = filterFunc(strings.HasSuffix, ".xml")
SbomSpdx = filterFunc(strings.HasSuffix, ".spdx")
SbomDsdx = filterFunc(strings.HasSuffix, ".dsdx")
SbomJson = filterFunc(strings.HasSuffix, ".json")
SbomXml = filterFunc(strings.HasSuffix, ".xml")
SbomDbSbom = filterFunc(strings.HasSuffix, ".dbsbom")
// SbomRdf = filterFunc(strings.HasSuffix, ".rdf")
)

Expand Down
65 changes: 65 additions & 0 deletions opensca/sca/sbom/dpsbom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package sbom

import (
"encoding/json"
"io"

"github.com/xmirrorsecurity/opensca-cli/v3/opensca/model"
)

func ParseDpSbomJson(f *model.File) *model.DepGraph {
doc := &model.DpSbomDocument{}
f.OpenReader(func(reader io.Reader) {
json.NewDecoder(reader).Decode(doc)
})
return parseDpSbomDoc(f, doc)
}

func parseDpSbomDoc(f *model.File, doc *model.DpSbomDocument) *model.DepGraph {

if doc == nil {
return nil
}

depIdMap := map[string]*model.DepGraph{}
_dep := model.NewDepGraphMap(func(s ...string) string {
return s[0]
}, func(s ...string) *model.DepGraph {
vendor, name, version, language := model.ParsePurl(s[0])
return &model.DepGraph{
Vendor: vendor,
Name: name,
Version: version,
Language: language,
}
}).LoadOrStore

for _, pkg := range doc.Packages {
dep := _dep(pkg.Identifier.Purl)
dep.Licenses = pkg.License
depIdMap[pkg.Identifier.Purl] = dep
}

for _, dependOn := range doc.Dependencies {
parent, ok := depIdMap[dependOn.Ref]
if !ok {
continue
}
for _, dep := range dependOn.DependsOn {
child, ok := depIdMap[dep.Target]
if !ok {
continue
}
parent.AppendChild(child)
}
}

root := &model.DepGraph{Path: f.Relpath()}
for _, dep := range depIdMap {
if len(dep.Parents) == 0 {
root.AppendChild(dep)
}
}

return root
}
4 changes: 4 additions & 0 deletions opensca/sca/sbom/sca.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,14 @@ func (sca Sca) Sca(ctx context.Context, parent *model.File, files []*model.File,
if filter.SbomDsdx(file.Relpath()) {
call(file, ParseDsdx(file))
}
if filter.SbomDbSbom(file.Relpath()) {
call(file, ParseDpSbomJson(file))
}
if filter.SbomJson(file.Relpath()) {
call(file, ParseSpdxJson(file))
call(file, ParseCdxJson(file))
call(file, ParseDsdxJson(file))
call(file, ParseDpSbomJson(file))
}
if filter.SbomXml(file.Relpath()) {
call(file, ParseSpdxXml(file))
Expand Down
Loading