diff --git a/cmd/format/bomsw.go b/cmd/format/bomsw.go new file mode 100644 index 0000000..86d980c --- /dev/null +++ b/cmd/format/bomsw.go @@ -0,0 +1,54 @@ +package format + +import ( + "encoding/json" + "io" + + "github.com/xmirrorsecurity/opensca-cli/v3/cmd/detail" + "github.com/xmirrorsecurity/opensca-cli/v3/opensca/model" +) + +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 bomSWDoc(report Report) *model.BomSWDocument { + + doc := model.NewBomSWDocument(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(swc *model.BomSWComponent) { + swc.ID = n.Purl() + swc.Name = n.Name + swc.Version = n.Version + swc.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 +} diff --git a/cmd/format/dpsbom.go b/cmd/format/dpsbom.go deleted file mode 100644 index 59be656..0000000 --- a/cmd/format/dpsbom.go +++ /dev/null @@ -1,117 +0,0 @@ -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 -} diff --git a/cmd/format/save.go b/cmd/format/save.go index 8d2ee38..4bc0313 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/bomsw.go b/opensca/model/bomsw.go new file mode 100644 index 0000000..1cde530 --- /dev/null +++ b/opensca/model/bomsw.go @@ -0,0 +1,125 @@ +package model + +import ( + "time" +) + +type BomSWDocument struct { + Basic swBasicInfo `json:"documentBasicInfo"` + Software swSoftwareCompositionInfo `json:"softwareCompositionInfo"` +} + +type swBasicInfo struct { + // 文档名称 + DocumentName string `json:"documentName"` + // 文档版本 + DocumentVersion string `json:"documentVersion"` + // 文档创建/更新时间 yyyy-MM-ddTHH:mm:ssTZD + DocumentTime string `json:"timestamp"` + // 文档格式 + SbomFormat string `json:"sbomFormat"` + // 生成工具 + ToolInfo string `json:"toolInfo"` + // bom作者 + SbomAuthor string `json:"sbomAuthor"` + // 文档作者注释 + SbomAuthorComments string `json:"sbomAuthorComments"` + // 文档注释 + SbomComments string `json:"sbomComments"` + // 文档类型 + SbomType string `json:"sbomType"` +} + +type swSoftwareCompositionInfo struct { + // 组件列表 + Components []BomSWComponent `json:"components"` + // 依赖关系 + Dependencies []swDependencies `json:"dependencies"` +} + +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:"componentTimestamp"` +} + +type swChecksumValue struct { + Algorithm string `json:"algorithm"` + Value string `json:"hashValue"` +} + +type swDependencies struct { + Ref string `json:"ref"` + DependsOn []struct { + Ref string `json:"ref"` + } `json:"dependsOn"` +} + +func newDependencies(ref string, dependsOn []string) swDependencies { + deps := swDependencies{Ref: ref} + deps.DependsOn = make([]struct { + Ref string `json:"ref"` + }, len(dependsOn)) + for i, d := range dependsOn { + deps.DependsOn[i].Ref = d + } + return deps +} + +func NewBomSWDocument(name, creator string) *BomSWDocument { + version := "1.0.0" + timestamp := time.Now().Format("2006-01-02T15:04:05MST") + 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{}, + }, + } +} + +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") + } + doc.Software.Components = append(doc.Software.Components, c) +} + +func (doc *BomSWDocument) AppendDependencies(parentId string, childrenIds []string) { + if doc.Software.Dependencies == nil { + doc.Software.Dependencies = []swDependencies{} + } + if len(childrenIds) > 0 { + doc.Software.Dependencies = append(doc.Software.Dependencies, newDependencies(parentId, childrenIds)) + } +} diff --git a/opensca/model/dpsbom.go b/opensca/model/dpsbom.go deleted file mode 100644 index 1786bbf..0000000 --- a/opensca/model/dpsbom.go +++ /dev/null @@ -1,112 +0,0 @@ -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)) - } -} 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 c3a70c3..83995a5 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 8d0c41c..b221eae 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))