From c1bf0edcfe5157fc91bf8451c32a330d8c47ca2e Mon Sep 17 00:00:00 2001 From: luotianqi777 Date: Tue, 22 Jul 2025 10:59:46 +0800 Subject: [PATCH 1/2] feat: support bomsw --- cmd/format/dpsbom.go | 89 ++----------- cmd/format/save.go | 14 +-- opensca/model/dpsbom.go | 151 ++++++++++++----------- opensca/sca/sbom/{dpsbom.go => bomsw.go} | 18 +-- opensca/sca/sbom/sca.go | 4 +- 5 files changed, 110 insertions(+), 166 deletions(-) rename opensca/sca/sbom/{dpsbom.go => bomsw.go} (70%) diff --git a/cmd/format/dpsbom.go b/cmd/format/dpsbom.go index 59be656b..86d980ce 100644 --- a/cmd/format/dpsbom.go +++ b/cmd/format/dpsbom.go @@ -1,88 +1,25 @@ 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 BomSWJson(report Report, out string) { + outWrite(out, func(w io.Writer) error { + doc := bomSWDoc(report) + encoder := json.NewEncoder(w) + encoder.SetIndent("", " ") + return encoder.Encode(doc) }) } -func pdSbomDoc(report Report) *model.DpSbomDocument { +func bomSWDoc(report Report) *model.BomSWDocument { - doc := model.NewDpSbomDocument(report.TaskInfo.AppName, "opensca-cli") + doc := model.NewBomSWDocument(report.TaskInfo.AppName, "opensca-cli") report.DepDetailGraph.ForEach(func(n *detail.DepDetailGraph) bool { @@ -94,11 +31,11 @@ func pdSbomDoc(report Report) *model.DpSbomDocument { 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 + doc.AppendComponents(func(swc *model.BomSWComponent) { + swc.ID = n.Purl() + swc.Name = n.Name + swc.Version = n.Version + swc.License = lics }) children := []string{} diff --git a/cmd/format/save.go b/cmd/format/save.go index 8d2ee382..4bc03136 100644 --- a/cmd/format/save.go +++ b/cmd/format/save.go @@ -39,12 +39,6 @@ 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) @@ -54,13 +48,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 if strings.HasSuffix(out, ".bomsw.json") { + BomSWJson(report, out) } else { Json(genReport(report), out) } - case ".dpsbom": - DpSbomZip(report, out) + case ".sw", ".bom-sw", ".bomsw": + BomSWJson(report, out) case ".dsdx": Dsdx(report, out) case ".spdx": diff --git a/opensca/model/dpsbom.go b/opensca/model/dpsbom.go index 1786bbf4..1cde5304 100644 --- a/opensca/model/dpsbom.go +++ b/opensca/model/dpsbom.go @@ -1,112 +1,125 @@ package model -import "time" +import ( + "time" +) -type DpSbomDocument struct { +type BomSWDocument struct { + Basic swBasicInfo `json:"documentBasicInfo"` + Software swSoftwareCompositionInfo `json:"softwareCompositionInfo"` +} + +type swBasicInfo struct { // 文档名称 - DocumentName string `json:"DocumentName"` + DocumentName string `json:"documentName"` // 文档版本 - DocumentVersion string `json:"DocumentVersion"` + DocumentVersion string `json:"documentVersion"` // 文档创建/更新时间 yyyy-MM-ddTHH:mm:ssTZD - DocumentTime string `json:"DocumentTime"` + DocumentTime string `json:"timestamp"` // 文档格式 - BomFormat string `json:"BomFormat"` + SbomFormat string `json:"sbomFormat"` // 生成工具 - Tool string `json:"tool"` - // sbom签名信息 - Hashes DpSbomHashes `json:"Hashes"` + ToolInfo string `json:"toolInfo"` + // bom作者 + SbomAuthor string `json:"sbomAuthor"` + // 文档作者注释 + SbomAuthorComments string `json:"sbomAuthorComments"` + // 文档注释 + SbomComments string `json:"sbomComments"` + // 文档类型 + SbomType string `json:"sbomType"` +} + +type swSoftwareCompositionInfo struct { // 组件列表 - Packages []DpSbomPackage `json:"Packages"` + Components []BomSWComponent `json:"components"` // 依赖关系 - Dependencies []DpSbomDependencies `json:"Dependencies"` + Dependencies []swDependencies `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"` - +type BomSWComponent struct { + Author map[string]string `json:"componentAuthor"` + Provider map[string]string `json:"componentProvider"` + Name string `json:"componentName"` + Version string `json:"componentVersion"` + // map[hash算法]hash值 + HashValue []swChecksumValue `json:"componentHashValue"` + ID string `json:"componentId"` + License []string `json:"license"` // 组件信息更新时间 yyyy-MM-ddTHH:mm:ssTZD - Timestamp string `json:"Timestamp"` + Timestamp string `json:"componentTimestamp"` +} + +type swChecksumValue struct { + Algorithm string `json:"algorithm"` + Value string `json:"hashValue"` } -type DpSbomDependencies struct { - Ref string `json:"Ref"` +type swDependencies struct { + Ref string `json:"ref"` DependsOn []struct { - Target string `json:"Target"` - } `json:"DependsOn"` + Ref string `json:"ref"` + } `json:"dependsOn"` } -func newDependencies(ref string, dependsOn []string) DpSbomDependencies { - deps := DpSbomDependencies{Ref: ref} +func newDependencies(ref string, dependsOn []string) swDependencies { + deps := swDependencies{Ref: ref} deps.DependsOn = make([]struct { - Target string "json:\"Target\"" + Ref string `json:"ref"` }, len(dependsOn)) for i, d := range dependsOn { - deps.DependsOn[i].Target = d + deps.DependsOn[i].Ref = 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 { +func NewBomSWDocument(name, creator string) *BomSWDocument { 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", + return &BomSWDocument{ + Basic: swBasicInfo{ + DocumentName: name, + DocumentVersion: version, + DocumentTime: timestamp, + SbomFormat: "BOM-SW 1.0", + ToolInfo: creator, + SbomAuthor: "", + SbomAuthorComments: "", + SbomComments: "", + SbomType: "analyzed", + }, + Software: swSoftwareCompositionInfo{ + Dependencies: []swDependencies{}, }, - Dependencies: []DpSbomDependencies{}, } } -func (doc *DpSbomDocument) AppendComponents(fn func(*DpSbomPackage)) { - c := DpSbomPackage{} +func (doc *BomSWDocument) AppendComponents(fn func(*BomSWComponent)) { + c := BomSWComponent{ + Author: map[string]string{ + "name": "NONE", + }, + Provider: map[string]string{ + "shortName": "NONE", + "fullName": "NONE", + }, + HashValue: []swChecksumValue{}, + License: []string{}, + } 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) + doc.Software.Components = append(doc.Software.Components, c) } -func (doc *DpSbomDocument) AppendDependencies(parentId string, childrenIds []string) { - if doc.Dependencies == nil { - doc.Dependencies = []DpSbomDependencies{} +func (doc *BomSWDocument) AppendDependencies(parentId string, childrenIds []string) { + if doc.Software.Dependencies == nil { + doc.Software.Dependencies = []swDependencies{} } if len(childrenIds) > 0 { - doc.Dependencies = append(doc.Dependencies, newDependencies(parentId, childrenIds)) + doc.Software.Dependencies = append(doc.Software.Dependencies, newDependencies(parentId, childrenIds)) } } diff --git a/opensca/sca/sbom/dpsbom.go b/opensca/sca/sbom/bomsw.go similarity index 70% rename from opensca/sca/sbom/dpsbom.go rename to opensca/sca/sbom/bomsw.go index c3a70c3f..83995a5a 100644 --- a/opensca/sca/sbom/dpsbom.go +++ b/opensca/sca/sbom/bomsw.go @@ -7,15 +7,15 @@ import ( "github.com/xmirrorsecurity/opensca-cli/v3/opensca/model" ) -func ParseDpSbomJson(f *model.File) *model.DepGraph { - doc := &model.DpSbomDocument{} +func ParseBomSWJson(f *model.File) *model.DepGraph { + doc := &model.BomSWDocument{} f.OpenReader(func(reader io.Reader) { json.NewDecoder(reader).Decode(doc) }) - return parseDpSbomDoc(f, doc) + return parseBomSWDoc(f, doc) } -func parseDpSbomDoc(f *model.File, doc *model.DpSbomDocument) *model.DepGraph { +func parseBomSWDoc(f *model.File, doc *model.BomSWDocument) *model.DepGraph { if doc == nil { return nil @@ -34,19 +34,19 @@ func parseDpSbomDoc(f *model.File, doc *model.DpSbomDocument) *model.DepGraph { } }).LoadOrStore - for _, pkg := range doc.Packages { - dep := _dep(pkg.Identifier.Purl) + for _, pkg := range doc.Software.Components { + dep := _dep(pkg.ID) dep.Licenses = pkg.License - depIdMap[pkg.Identifier.Purl] = dep + depIdMap[pkg.ID] = dep } - for _, dependOn := range doc.Dependencies { + for _, dependOn := range doc.Software.Dependencies { parent, ok := depIdMap[dependOn.Ref] if !ok { continue } for _, dep := range dependOn.DependsOn { - child, ok := depIdMap[dep.Target] + child, ok := depIdMap[dep.Ref] if !ok { continue } diff --git a/opensca/sca/sbom/sca.go b/opensca/sca/sbom/sca.go index 8d0c41ce..b221eae7 100644 --- a/opensca/sca/sbom/sca.go +++ b/opensca/sca/sbom/sca.go @@ -26,13 +26,13 @@ func (sca Sca) Sca(ctx context.Context, parent *model.File, files []*model.File, call(file, ParseDsdx(file)) } if filter.SbomDbSbom(file.Relpath()) { - call(file, ParseDpSbomJson(file)) + call(file, ParseBomSWJson(file)) } if filter.SbomJson(file.Relpath()) { call(file, ParseSpdxJson(file)) call(file, ParseCdxJson(file)) call(file, ParseDsdxJson(file)) - call(file, ParseDpSbomJson(file)) + call(file, ParseBomSWJson(file)) } if filter.SbomXml(file.Relpath()) { call(file, ParseSpdxXml(file)) From 524b93322516a92914fa21564108c2a3180d3fe9 Mon Sep 17 00:00:00 2001 From: luotianqi777 Date: Tue, 22 Jul 2025 11:11:14 +0800 Subject: [PATCH 2/2] dpsbom rename bomsw --- cmd/format/{dpsbom.go => bomsw.go} | 0 opensca/model/{dpsbom.go => bomsw.go} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename cmd/format/{dpsbom.go => bomsw.go} (100%) rename opensca/model/{dpsbom.go => bomsw.go} (100%) diff --git a/cmd/format/dpsbom.go b/cmd/format/bomsw.go similarity index 100% rename from cmd/format/dpsbom.go rename to cmd/format/bomsw.go diff --git a/opensca/model/dpsbom.go b/opensca/model/bomsw.go similarity index 100% rename from opensca/model/dpsbom.go rename to opensca/model/bomsw.go