Skip to content

Commit

Permalink
Merge pull request #60 from interlynk-io/51-hierarchical-merges-assum…
Browse files Browse the repository at this point in the history
…es-dependencies-should-it-not-allow-simple-containment

Add assembly sbom feature for cyclonedx
  • Loading branch information
riteshnoronha authored Jul 6, 2024
2 parents 3a20d56 + af65970 commit 976070c
Show file tree
Hide file tree
Showing 10 changed files with 151 additions and 37 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ for input and output formats
We currently support two algorithm
- Hierarchical: This merge algo tries to maintain, the order of the dependent components to its primary component. For spdx this is done via relationships and for cyclonedx via nested components & dependencies.
- Flat: As the name states, are just consolidated lists of components, dependencies, etc.
- Assembly: Merge is very similar to Hierarchical, except that it does not create dependency relationships among the merged sboms.

For `spdx hierarchical merge`, all packages, dependencies, externalrefs, files are consolidates into a individual lists, no duplicates are removed. The hierarchy is maintained via dependencies. A new primary package is created, which the generated SBOM describes. This primary package also adds contains
relationship between itself and the primary components of the individual SBOMs.
Expand Down
11 changes: 6 additions & 5 deletions cmd/assemble.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,13 @@ func init() {
assembleCmd.MarkFlagsRequiredTogether("name", "version", "type")

assembleCmd.Flags().BoolP("flatMerge", "f", false, "flat merge")
assembleCmd.Flags().BoolP("hierMerge", "m", true, "hierarchical merge")
assembleCmd.Flags().BoolP("hierMerge", "m", false, "hierarchical merge")
assembleCmd.Flags().BoolP("assemblyMerge", "a", false, "assembly merge")
assembleCmd.MarkFlagsMutuallyExclusive("flatMerge", "hierMerge", "assemblyMerge")

assembleCmd.Flags().BoolP("xml", "x", false, "output in xml format")
assembleCmd.Flags().BoolP("json", "j", true, "output in json format")
assembleCmd.MarkFlagsMutuallyExclusive("xml", "json")

assembleCmd.PersistentFlags().BoolP("debug", "d", false, "debug output")
}
Expand Down Expand Up @@ -125,13 +128,11 @@ func extractArgs(cmd *cobra.Command, args []string) (*assemble.Params, error) {

flatMerge, _ := cmd.Flags().GetBool("flatMerge")
hierMerge, _ := cmd.Flags().GetBool("hierMerge")

if flatMerge {
hierMerge = false
}
assemblyMerge, _ := cmd.Flags().GetBool("assemblyMerge")

aParams.FlatMerge = flatMerge
aParams.HierMerge = hierMerge
aParams.AssemblyMerge = assemblyMerge

xml, _ := cmd.Flags().GetBool("xml")
json, _ := cmd.Flags().GetBool("json")
Expand Down
5 changes: 4 additions & 1 deletion pkg/assemble/cdx/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ type assemble struct {
IncludeDuplicateComponents bool
FlatMerge bool
HierarchicalMerge bool
AssemblyMerge bool
}

type MergeSettings struct {
Expand All @@ -129,7 +130,9 @@ func Merge(ms *MergeSettings) error {
return merger.flatMerge()
} else if ms.Assemble.HierarchicalMerge {
return merger.hierarchicalMerge()
} else if ms.Assemble.AssemblyMerge {
return merger.assemblyMerge()
}

return nil
return merger.hierarchicalMerge()
}
97 changes: 79 additions & 18 deletions pkg/assemble/cdx/merge.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
cydx "github.com/CycloneDX/cyclonedx-go"
"github.com/interlynk-io/sbomasm/pkg/logger"
"github.com/samber/lo"
"sigs.k8s.io/release-utils/version"
)

type merge struct {
Expand Down Expand Up @@ -52,9 +51,6 @@ func (m *merge) initOutBom() {
m.out.SerialNumber = newSerialNumber()
m.out.Metadata = &cydx.Metadata{}
m.out.Metadata.Timestamp = utcNowTime()
m.out.Metadata.Tools.Tools = &[]cydx.Tool{
{Vendor: "Interlynk.io", Name: "sbomasm", Version: version.GetVersionInfo().GitVersion},
}

if m.settings.App.Supplier.Name != "" || m.settings.App.Supplier.Email != "" {
m.out.Metadata.Supplier = &cydx.OrganizationalEntity{}
Expand Down Expand Up @@ -146,12 +142,6 @@ func (m *merge) flatMerge() error {
cs := newComponentService(*m.settings.Ctx)

log.Debug("Merging BOMs into a flat list")
tools := lo.Flatten(lo.Map(m.in, func(bom *cydx.BOM, _ int) []cydx.Tool {
if bom.Metadata.Tools != nil {
return *bom.Metadata.Tools.Tools
}
return []cydx.Tool{}
}))

priComps := lo.Map(m.in, func(bom *cydx.BOM, _ int) *cydx.Component {
if bom.Metadata != nil && bom.Metadata.Component != nil {
Expand Down Expand Up @@ -188,7 +178,12 @@ func (m *merge) flatMerge() error {
}))

m.out.Metadata.Component = m.setupPrimaryComp()
m.out.Metadata.Tools.Tools = &tools

tools := getAllTools(m.in)
m.out.Metadata.Tools = &cydx.ToolsChoice{
Components: &[]cydx.Component{},
}
*m.out.Metadata.Tools.Components = append(*m.out.Metadata.Tools.Components, tools...)

//Add the primary component to the list of components
for _, c := range priComps {
Expand Down Expand Up @@ -217,19 +212,80 @@ func (m *merge) flatMerge() error {

}

func (m *merge) hierarchicalMerge() error {
func (m *merge) assemblyMerge() error {
log := logger.FromContext(*m.settings.Ctx)
cs := newComponentService(*m.settings.Ctx)

log.Debug("Merging BOMs hierarchically")
log.Debug("Merging BOMs as an assembly")

priComps := lo.Map(m.in, func(bom *cydx.BOM, _ int) *cydx.Component {
if bom.Metadata != nil && bom.Metadata.Component != nil {
pc := cs.StoreAndCloneWithNewID(bom.Metadata.Component)

if pc.Components == nil {
pc.Components = &[]cydx.Component{}
}

for _, c := range lo.FromPtr(bom.Components) {
*pc.Components = append(*pc.Components, *cs.StoreAndCloneWithNewID(&c))
}
return pc
}
return &cydx.Component{}
})

deps := lo.Flatten(lo.Map(m.in, func(bom *cydx.BOM, _ int) []cydx.Dependency {
newDeps := []cydx.Dependency{}
for _, dep := range lo.FromPtr(bom.Dependencies) {
nd := cydx.Dependency{}
ref, found := cs.ResolveDepID(dep.Ref)
if !found {
log.Warnf("dependency %s not found", dep.Ref)
continue
}

deps := cs.ResolveDepIDs(lo.FromPtr(dep.Dependencies))

tools := lo.Flatten(lo.Map(m.in, func(bom *cydx.BOM, _ int) []cydx.Tool {
if bom.Metadata.Tools != nil {
return *bom.Metadata.Tools.Tools
nd.Ref = ref
nd.Dependencies = &deps
newDeps = append(newDeps, nd)
}
return []cydx.Tool{}
return newDeps
}))

m.out.Metadata.Component = m.setupPrimaryComp()

m.out.Metadata.Component.Components = &[]cydx.Component{}
for _, c := range priComps {
*m.out.Metadata.Component.Components = append(*m.out.Metadata.Component.Components, *c)
}

tools := getAllTools(m.in)
m.out.Metadata.Tools = &cydx.ToolsChoice{
Components: &[]cydx.Component{},
}
*m.out.Metadata.Tools.Components = append(*m.out.Metadata.Tools.Components, tools...)

if m.settings.Assemble.IncludeComponents {
m.out.Components = &[]cydx.Component{}
for _, c := range priComps {
*m.out.Components = append(*m.out.Components, *c)
}
}

if m.settings.Assemble.IncludeDependencyGraph {
m.out.Dependencies = &deps
}

return m.writeSBOM()
}

func (m *merge) hierarchicalMerge() error {
log := logger.FromContext(*m.settings.Ctx)
cs := newComponentService(*m.settings.Ctx)

log.Debug("Merging BOMs hierarchically")

priComps := lo.Map(m.in, func(bom *cydx.BOM, _ int) *cydx.Component {
if bom.Metadata != nil && bom.Metadata.Component != nil {
pc := cs.StoreAndCloneWithNewID(bom.Metadata.Component)
Expand Down Expand Up @@ -266,7 +322,12 @@ func (m *merge) hierarchicalMerge() error {
}))

m.out.Metadata.Component = m.setupPrimaryComp()
m.out.Metadata.Tools.Tools = &tools

tools := getAllTools(m.in)
m.out.Metadata.Tools = &cydx.ToolsChoice{
Components: &[]cydx.Component{},
}
*m.out.Metadata.Tools.Components = append(*m.out.Metadata.Tools.Components, tools...)

//Add depedencies between new primary component and old primary components
priIds := lo.Map(priComps, func(c *cydx.Component, _ int) string {
Expand Down
43 changes: 43 additions & 0 deletions pkg/assemble/cdx/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/interlynk-io/sbomasm/pkg/logger"
"github.com/mitchellh/copystructure"
"github.com/mitchellh/hashstructure/v2"
"sigs.k8s.io/release-utils/version"
)

func newSerialNumber() string {
Expand Down Expand Up @@ -96,3 +97,45 @@ func utcNowTime() string {
locationTime := time.Now().In(location)
return locationTime.Format(time.RFC3339)
}

func getAllTools(boms []*cydx.BOM) []cydx.Component {
tools := []cydx.Component{}

tools = append(tools, *toolInfo("sbomasm", version.GetVersionInfo().GitVersion, "Assembler for your sboms", "Interlynk", "https://interlynk.io", "[email protected]", "Apache-2.0"))

for _, bom := range boms {
if bom.Metadata != nil && bom.Metadata.Tools != nil {
for _, tool := range *bom.Metadata.Tools.Tools {
tools = append(tools, *toolInfo(tool.Name, tool.Version, "", tool.Vendor, "", "", ""))
}
}

if bom.Metadata != nil && bom.Metadata.Tools != nil && bom.Metadata.Tools.Components != nil {
for _, tool := range *bom.Metadata.Tools.Components {
tools = append(tools, *toolInfo(tool.Name, tool.Version, "", "", "", "", ""))
}
}
}
return tools
}

func toolInfo(name, version, desc, sName, sUrl, sEmail, sLicense string) *cydx.Component {
return &cydx.Component{
Type: cydx.ComponentTypeApplication,
Name: name,
Version: version,
Description: desc,
Supplier: &cydx.OrganizationalEntity{
Name: sName,
URL: &[]string{sUrl},
Contact: &[]cydx.OrganizationalContact{{Email: sEmail}},
},
Licenses: &cydx.Licenses{
{
License: &cydx.License{
ID: sLicense,
},
},
},
}
}
1 change: 1 addition & 0 deletions pkg/assemble/combiner.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ func toCDXMergerSettings(c *config) *cdx.MergeSettings {

ms.Assemble.FlatMerge = c.Assemble.FlatMerge
ms.Assemble.HierarchicalMerge = c.Assemble.HierarchicalMerge
ms.Assemble.AssemblyMerge = c.Assemble.AssemblyMerge
ms.Assemble.IncludeComponents = c.Assemble.IncludeComponents
ms.Assemble.IncludeDuplicateComponents = c.Assemble.includeDuplicateComponents
ms.Assemble.IncludeDependencyGraph = c.Assemble.IncludeDependencyGraph
Expand Down
14 changes: 4 additions & 10 deletions pkg/assemble/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (
"crypto/sha256"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"strings"
Expand Down Expand Up @@ -85,6 +84,7 @@ type assemble struct {
includeDuplicateComponents bool
FlatMerge bool `yaml:"flat_merge"`
HierarchicalMerge bool `yaml:"hierarchical_merge"`
AssemblyMerge bool `yaml:"assembly_merge"`
}

type config struct {
Expand Down Expand Up @@ -125,6 +125,7 @@ var defaultConfig = config{
Assemble: assemble{
FlatMerge: false,
HierarchicalMerge: true,
AssemblyMerge: false,
IncludeComponents: true,
IncludeDependencyGraph: true,
includeDuplicateComponents: true,
Expand Down Expand Up @@ -158,7 +159,7 @@ func newConfig() *config {
func (c *config) readAndMerge(p *Params) error {
if p.ConfigPath != "" {

yF, err := ioutil.ReadFile(p.ConfigPath)
yF, err := os.ReadFile(p.ConfigPath)
if err != nil {
return err
}
Expand All @@ -171,6 +172,7 @@ func (c *config) readAndMerge(p *Params) error {

c.Assemble.FlatMerge = p.FlatMerge
c.Assemble.HierarchicalMerge = p.HierMerge
c.Assemble.AssemblyMerge = p.AssemblyMerge
}

c.input.files = p.Input
Expand Down Expand Up @@ -276,14 +278,6 @@ func (c *config) validate() error {
return fmt.Errorf("input files are not set")
}

if !c.Assemble.FlatMerge && !c.Assemble.HierarchicalMerge {
return fmt.Errorf("flat merge or hierarchical merge must be set")
}

if c.Assemble.FlatMerge && c.Assemble.HierarchicalMerge {
return fmt.Errorf("flat merge or hierarchical merger can be set, not both")
}

err := c.validateInputContent()
if err != nil {
return err
Expand Down
5 changes: 3 additions & 2 deletions pkg/assemble/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ type Params struct {
Version string
Type string

FlatMerge bool
HierMerge bool
FlatMerge bool
HierMerge bool
AssemblyMerge bool

Xml bool
Json bool
Expand Down
5 changes: 5 additions & 0 deletions pkg/assemble/spdx/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ type assemble struct {
IncludeDuplicateComponents bool
FlatMerge bool
HierarchicalMerge bool
AssemblyMerge bool
}

type MergeSettings struct {
Expand All @@ -105,6 +106,10 @@ func Merge(ms *MergeSettings) error {
return merger.flatMerge()
} else if ms.Assemble.HierarchicalMerge {
return merger.hierarchicalMerge()
} else if ms.Assemble.AssemblyMerge {
return merger.assemblyMerge()
} else {
return merger.hierarchicalMerge()
}

return nil
Expand Down
6 changes: 5 additions & 1 deletion pkg/assemble/spdx/merge.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,11 @@ func (m *merge) hierarchicalMerge() error {
}

func (m *merge) flatMerge() error {
return fmt.Errorf("spdx flat merge not implemented")
return fmt.Errorf("not implemented")
}

func (m *merge) assemblyMerge() error {
return fmt.Errorf("not implemented")
}

func (m *merge) writeSBOM() error {
Expand Down

0 comments on commit 976070c

Please sign in to comment.