Skip to content

Commit

Permalink
Merge pull request #307 from luotianqi777/dpsbom
Browse files Browse the repository at this point in the history
feat: 支持Dpsbom
  • Loading branch information
cyberchen1995 authored Jan 17, 2025
2 parents afb22dc + eb0e7e9 commit ff03ff4
Show file tree
Hide file tree
Showing 6 changed files with 313 additions and 4 deletions.
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

0 comments on commit ff03ff4

Please sign in to comment.