Skip to content

Commit ff03ff4

Browse files
Merge pull request #307 from luotianqi777/dpsbom
feat: 支持Dpsbom
2 parents afb22dc + eb0e7e9 commit ff03ff4

File tree

6 files changed

+313
-4
lines changed

6 files changed

+313
-4
lines changed

cmd/format/dpsbom.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package format
2+
3+
import (
4+
"archive/zip"
5+
"crypto/md5"
6+
"crypto/sha1"
7+
"crypto/sha256"
8+
"encoding/hex"
9+
"encoding/json"
10+
"errors"
11+
"fmt"
12+
"hash"
13+
"io"
14+
"path/filepath"
15+
"strings"
16+
17+
"github.com/xmirrorsecurity/opensca-cli/v3/cmd/detail"
18+
"github.com/xmirrorsecurity/opensca-cli/v3/opensca/model"
19+
)
20+
21+
func DpSbomZip(report Report, out string) {
22+
zipFile := out
23+
if !strings.HasSuffix(out, ".zip") {
24+
zipFile = out + ".zip"
25+
}
26+
jsonName := filepath.Base(out)
27+
if !strings.HasSuffix(jsonName, ".json") {
28+
jsonName = jsonName + ".json"
29+
}
30+
outWrite(zipFile, func(w io.Writer) error {
31+
doc := pdSbomDoc(report)
32+
if doc.Hashes.HashFile == "" {
33+
return errors.New("hash file is required")
34+
}
35+
36+
var h hash.Hash
37+
switch strings.ToLower(doc.Hashes.Algorithm) {
38+
case "sha-256":
39+
h = sha256.New()
40+
case "sha-1":
41+
h = sha1.New()
42+
case "md5":
43+
h = md5.New()
44+
case "":
45+
return errors.New("hash algorithm is required")
46+
default:
47+
return fmt.Errorf("unsupported hash algorithm: %s", doc.Hashes.Algorithm)
48+
}
49+
50+
tojson := func(w io.Writer) error {
51+
encoder := json.NewEncoder(w)
52+
encoder.SetIndent("", " ")
53+
return encoder.Encode(doc)
54+
}
55+
56+
zipfile := zip.NewWriter(w)
57+
defer zipfile.Close()
58+
59+
sbomfile, err := zipfile.Create(jsonName)
60+
if err != nil {
61+
return err
62+
}
63+
err = tojson(sbomfile)
64+
if err != nil {
65+
return err
66+
}
67+
68+
hashfile, err := zipfile.Create(doc.Hashes.HashFile)
69+
if err != nil {
70+
return err
71+
}
72+
err = tojson(h)
73+
if err != nil {
74+
return err
75+
}
76+
hashstr := hex.EncodeToString(h.Sum(nil)[:])
77+
hashfile.Write([]byte(hashstr))
78+
79+
return nil
80+
})
81+
}
82+
83+
func pdSbomDoc(report Report) *model.DpSbomDocument {
84+
85+
doc := model.NewDpSbomDocument(report.TaskInfo.AppName, "opensca-cli")
86+
87+
report.DepDetailGraph.ForEach(func(n *detail.DepDetailGraph) bool {
88+
89+
if n.Name == "" {
90+
return true
91+
}
92+
93+
lics := []string{}
94+
for _, lic := range n.Licenses {
95+
lics = append(lics, lic.ShortName)
96+
}
97+
doc.AppendComponents(func(dsp *model.DpSbomPackage) {
98+
dsp.Identifier.Purl = n.Purl()
99+
dsp.Name = n.Name
100+
dsp.Version = n.Version
101+
dsp.License = lics
102+
})
103+
104+
children := []string{}
105+
for _, c := range n.Children {
106+
if c.Name == "" {
107+
continue
108+
}
109+
children = append(children, c.Purl())
110+
}
111+
doc.AppendDependencies(n.Purl(), children)
112+
113+
return true
114+
})
115+
116+
return doc
117+
}

cmd/format/save.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ func Save(report Report, output string) {
3939
switch filepath.Ext(out) {
4040
case ".html":
4141
Html(genReport(report), out)
42+
case ".zip":
43+
if strings.HasSuffix(out, ".dpsbom.zip") {
44+
DpSbomZip(report, out)
45+
} else {
46+
Json(genReport(report), out)
47+
}
4248
case ".json":
4349
if strings.HasSuffix(out, ".spdx.json") {
4450
SpdxJson(report, out)
@@ -48,9 +54,13 @@ func Save(report Report, output string) {
4854
CycloneDXJson(report, out)
4955
} else if strings.HasSuffix(out, ".swid.json") {
5056
SwidJson(report, out)
57+
} else if strings.HasSuffix(out, ".dpsbom.json") {
58+
DpSbomZip(report, out)
5159
} else {
5260
Json(genReport(report), out)
5361
}
62+
case ".dpsbom":
63+
DpSbomZip(report, out)
5464
case ".dsdx":
5565
Dsdx(report, out)
5666
case ".spdx":

opensca/model/dpsbom.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package model
2+
3+
import "time"
4+
5+
type DpSbomDocument struct {
6+
// 文档名称
7+
DocumentName string `json:"DocumentName"`
8+
// 文档版本
9+
DocumentVersion string `json:"DocumentVersion"`
10+
// 文档创建/更新时间 yyyy-MM-ddTHH:mm:ssTZD
11+
DocumentTime string `json:"DocumentTime"`
12+
// 文档格式
13+
BomFormat string `json:"BomFormat"`
14+
// 生成工具
15+
Tool string `json:"tool"`
16+
// sbom签名信息
17+
Hashes DpSbomHashes `json:"Hashes"`
18+
// 组件列表
19+
Packages []DpSbomPackage `json:"Packages"`
20+
// 依赖关系
21+
Dependencies []DpSbomDependencies `json:"Dependencies"`
22+
}
23+
24+
type DpSbomPackage struct {
25+
Name string `json:"ComponentName"`
26+
Version string `json:"ComponentVersion"`
27+
28+
Identifier struct {
29+
Purl string `json:"PURL"`
30+
} `json:"ComponentIdentifier"`
31+
32+
License []string `json:"License"`
33+
34+
Author []map[string]string `json:"Author"`
35+
Provider []map[string]string `json:"Provider"`
36+
Hash DpSbomHash `json:"ComponentHash"`
37+
38+
// 组件信息更新时间 yyyy-MM-ddTHH:mm:ssTZD
39+
Timestamp string `json:"Timestamp"`
40+
}
41+
42+
type DpSbomDependencies struct {
43+
Ref string `json:"Ref"`
44+
DependsOn []struct {
45+
Target string `json:"Target"`
46+
} `json:"DependsOn"`
47+
}
48+
49+
func newDependencies(ref string, dependsOn []string) DpSbomDependencies {
50+
deps := DpSbomDependencies{Ref: ref}
51+
deps.DependsOn = make([]struct {
52+
Target string "json:\"Target\""
53+
}, len(dependsOn))
54+
for i, d := range dependsOn {
55+
deps.DependsOn[i].Target = d
56+
}
57+
return deps
58+
}
59+
60+
type DpSbomHashes struct {
61+
Algorithm string `json:"Algorithm"`
62+
HashFile string `json:"HashFile,omitempty"`
63+
DigitalFile string `json:"DigitalFile,omitempty"`
64+
}
65+
66+
type DpSbomHash struct {
67+
Algorithm string `json:"Algorithm,omitempty"`
68+
Hash string `json:"Hash,omitempty"`
69+
}
70+
71+
func NewDpSbomDocument(name, creator string) *DpSbomDocument {
72+
version := "1.0.0"
73+
timestamp := time.Now().Format("2006-01-02T15:04:05MST")
74+
return &DpSbomDocument{
75+
DocumentName: name,
76+
DocumentVersion: version,
77+
DocumentTime: timestamp,
78+
BomFormat: "DP-SBOM-1.0",
79+
Tool: creator,
80+
Hashes: DpSbomHashes{
81+
Algorithm: "SHA-256",
82+
HashFile: "sha256.txt",
83+
},
84+
Dependencies: []DpSbomDependencies{},
85+
}
86+
}
87+
88+
func (doc *DpSbomDocument) AppendComponents(fn func(*DpSbomPackage)) {
89+
c := DpSbomPackage{}
90+
if fn != nil {
91+
fn(&c)
92+
}
93+
if c.Timestamp == "" {
94+
c.Timestamp = time.Now().Format("2006-01-02T15:04:05MST")
95+
}
96+
if c.Author == nil {
97+
c.Author = []map[string]string{}
98+
}
99+
if c.Provider == nil {
100+
c.Provider = []map[string]string{}
101+
}
102+
doc.Packages = append(doc.Packages, c)
103+
}
104+
105+
func (doc *DpSbomDocument) AppendDependencies(parentId string, childrenIds []string) {
106+
if doc.Dependencies == nil {
107+
doc.Dependencies = []DpSbomDependencies{}
108+
}
109+
if len(childrenIds) > 0 {
110+
doc.Dependencies = append(doc.Dependencies, newDependencies(parentId, childrenIds))
111+
}
112+
}

opensca/sca/filter/filter.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,11 @@ var (
6969
)
7070

7171
var (
72-
SbomSpdx = filterFunc(strings.HasSuffix, ".spdx")
73-
SbomDsdx = filterFunc(strings.HasSuffix, ".dsdx")
74-
SbomJson = filterFunc(strings.HasSuffix, ".json")
75-
SbomXml = filterFunc(strings.HasSuffix, ".xml")
72+
SbomSpdx = filterFunc(strings.HasSuffix, ".spdx")
73+
SbomDsdx = filterFunc(strings.HasSuffix, ".dsdx")
74+
SbomJson = filterFunc(strings.HasSuffix, ".json")
75+
SbomXml = filterFunc(strings.HasSuffix, ".xml")
76+
SbomDbSbom = filterFunc(strings.HasSuffix, ".dbsbom")
7677
// SbomRdf = filterFunc(strings.HasSuffix, ".rdf")
7778
)
7879

opensca/sca/sbom/dpsbom.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package sbom
2+
3+
import (
4+
"encoding/json"
5+
"io"
6+
7+
"github.com/xmirrorsecurity/opensca-cli/v3/opensca/model"
8+
)
9+
10+
func ParseDpSbomJson(f *model.File) *model.DepGraph {
11+
doc := &model.DpSbomDocument{}
12+
f.OpenReader(func(reader io.Reader) {
13+
json.NewDecoder(reader).Decode(doc)
14+
})
15+
return parseDpSbomDoc(f, doc)
16+
}
17+
18+
func parseDpSbomDoc(f *model.File, doc *model.DpSbomDocument) *model.DepGraph {
19+
20+
if doc == nil {
21+
return nil
22+
}
23+
24+
depIdMap := map[string]*model.DepGraph{}
25+
_dep := model.NewDepGraphMap(func(s ...string) string {
26+
return s[0]
27+
}, func(s ...string) *model.DepGraph {
28+
vendor, name, version, language := model.ParsePurl(s[0])
29+
return &model.DepGraph{
30+
Vendor: vendor,
31+
Name: name,
32+
Version: version,
33+
Language: language,
34+
}
35+
}).LoadOrStore
36+
37+
for _, pkg := range doc.Packages {
38+
dep := _dep(pkg.Identifier.Purl)
39+
dep.Licenses = pkg.License
40+
depIdMap[pkg.Identifier.Purl] = dep
41+
}
42+
43+
for _, dependOn := range doc.Dependencies {
44+
parent, ok := depIdMap[dependOn.Ref]
45+
if !ok {
46+
continue
47+
}
48+
for _, dep := range dependOn.DependsOn {
49+
child, ok := depIdMap[dep.Target]
50+
if !ok {
51+
continue
52+
}
53+
parent.AppendChild(child)
54+
}
55+
}
56+
57+
root := &model.DepGraph{Path: f.Relpath()}
58+
for _, dep := range depIdMap {
59+
if len(dep.Parents) == 0 {
60+
root.AppendChild(dep)
61+
}
62+
}
63+
64+
return root
65+
}

opensca/sca/sbom/sca.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,14 @@ func (sca Sca) Sca(ctx context.Context, parent *model.File, files []*model.File,
2525
if filter.SbomDsdx(file.Relpath()) {
2626
call(file, ParseDsdx(file))
2727
}
28+
if filter.SbomDbSbom(file.Relpath()) {
29+
call(file, ParseDpSbomJson(file))
30+
}
2831
if filter.SbomJson(file.Relpath()) {
2932
call(file, ParseSpdxJson(file))
3033
call(file, ParseCdxJson(file))
3134
call(file, ParseDsdxJson(file))
35+
call(file, ParseDpSbomJson(file))
3236
}
3337
if filter.SbomXml(file.Relpath()) {
3438
call(file, ParseSpdxXml(file))

0 commit comments

Comments
 (0)