From f31c9141f6d3d01708e050be32e58650238906e7 Mon Sep 17 00:00:00 2001 From: Matt Rutkowski Date: Tue, 30 Apr 2024 16:18:55 -0500 Subject: [PATCH] Support ResourceInfo as an embedded struct in ComponentInfo Signed-off-by: Matt Rutkowski --- cmd/component.go | 76 +++----------------------------- cmd/license_list.go | 3 -- cmd/report.go | 7 ++- cmd/resource.go | 14 +++--- cmd/stats.go | 6 +-- schema/bom_hash.go | 42 +++++++++--------- schema/cyclonedx.go | 2 +- schema/cyclonedx_abstractions.go | 25 ++++++++++- 8 files changed, 67 insertions(+), 108 deletions(-) diff --git a/cmd/component.go b/cmd/component.go index 5c95e9e8..03c28190 100644 --- a/cmd/component.go +++ b/cmd/component.go @@ -39,38 +39,6 @@ const ( var VALID_SUBCOMMANDS_COMPONENT = []string{SUBCOMMAND_COMPONENT_LIST} -// type CDXComponent struct { -// Primary bool `json:"-"` // Proprietary: do NOT marshal/unmarshal -// Type string `json:"type,omitempty"` // Constraint: enum [see schema] -// Name string `json:"name,omitempty"` -// Version string `json:"version,omitempty"` -// Description string `json:"description,omitempty"` -// Group string `json:"group,omitempty"` -// BOMRef *CDXRefType `json:"bom-ref,omitempty"` -// MimeType string `json:"mime-type,omitempty"` -// Supplier *CDXOrganizationalEntity `json:"supplier,omitempty"` -// Author string `json:"author,omitempty"` // v1.6: deprecated. -// Publisher string `json:"publisher,omitempty"` -// Scope string `json:"scope,omitempty"` // Constraint: "enum": ["required","optional","excluded"] -// Hashes *[]CDXHash `json:"hashes,omitempty"` -// Licenses *[]CDXLicenseChoice `json:"licenses,omitempty"` -// Copyright string `json:"copyright,omitempty"` -// Cpe string `json:"cpe,omitempty"` // See: https://nvd.nist.gov/products/cpe -// Purl string `json:"purl,omitempty" scvs:"bom:resource:identifiers:purl"` // See: https://github.com/package-url/purl-spec -// Swid *CDXSwid `json:"swid,omitempty"` // See: https://www.iso.org/standard/65666.html -// Pedigree *CDXPedigree `json:"pedigree,omitempty"` // anon. type -// ExternalReferences *[]CDXExternalReference `json:"externalReferences,omitempty"` -// Components *[]CDXComponent `json:"components,omitempty"` -// Evidence *CDXComponentEvidence `json:"evidence,omitempty"` // v1.3: added -// Properties *[]CDXProperty `json:"properties,omitempty"` // v1.3: added -// Modified bool `json:"modified,omitempty" cdx:"deprecated"` // v1.4: deprecated -// ReleaseNotes *[]CDXReleaseNotes `json:"releaseNotes,omitempty"` // v1.4: added -// Signature *JSFSignature `json:"signature,omitempty"` // v1.4: added -// ModelCard *CDXModelCard `json:"modelCard,omitempty"` // v1.5: added -// Data *[]CDXComponentData `json:"data,omitempty"` // v1.5: added -// Authors *[]CDXOrganizationalContact `json:"authors,omitempty"` // v1.6: added -// Tags *[]string `json:"tags,omitempty" cdx:"+1.6"` // v1.6: added -// } var COMPONENT_LIST_ROW_DATA = []ColumnFormatData{ *NewColumnFormatData(COMPONENT_FILTER_KEY_TYPE, DEFAULT_COLUMN_TRUNCATE_LENGTH, REPORT_SUMMARY_DATA_TRUE, false), *NewColumnFormatData(COMPONENT_FILTER_KEY_NAME, DEFAULT_COLUMN_TRUNCATE_LENGTH, REPORT_SUMMARY_DATA_TRUE, false), @@ -94,13 +62,6 @@ var VALID_COMPONENT_FILTER_KEYS = []string{ COMPONENT_FILTER_KEY_BOMREF, } -// var COMPONENT_LIST_TITLES = []string{ -// COMPONENT_FILTER_KEY_TYPE, -// COMPONENT_FILTER_KEY_NAME, -// COMPONENT_FILTER_KEY_VERSION, -// COMPONENT_FILTER_KEY_BOMREF, -// } - // Flags. Reuse query flag values where possible const ( FLAG_COMPONENT_TYPE = "type" @@ -155,25 +116,6 @@ func NewCommandComponent() *cobra.Command { return command } -func retrieveComponentType(cmd *cobra.Command) (componentTypes string, err error) { - - componentTypes, err = cmd.Flags().GetString(FLAG_COMPONENT_TYPE) - if err != nil { - return - } - - // TODO: parse "type" flag (comma-separated list of type names) - // TODO: validate each one is supported in the CDX Component Type enum. - // // validate component type(s) is a known keyword - // TODO: support multiple types (slice) and return "invalid" as an error - // if !schema.IsValidComponentType(componentTypes) { - // // invalid - // err = getLogger().Errorf("invalid type `%s`: `%s`", FLAG_COMPONENT_TYPE, componentType) - // } - - return -} - func componentCmdImpl(cmd *cobra.Command, args []string) (err error) { getLogger().Enter(args) defer getLogger().Exit() @@ -195,14 +137,8 @@ func componentCmdImpl(cmd *cobra.Command, args []string) (err error) { // process filters supplied on the --where command flag whereFilters, err := processWhereFlag(cmd) - // Process flag: --type - var types string - var commandFlags utils.ComponentCommandFlags - types, err = retrieveComponentType(cmd) - if err == nil { - commandFlags.Types = types - err = ListComponents(writer, utils.GlobalFlags.PersistentFlags, commandFlags, whereFilters) + err = ListComponents(writer, utils.GlobalFlags.PersistentFlags, whereFilters) } return @@ -217,7 +153,7 @@ func processComponentListResults(err error) { } // NOTE: resourceType has already been validated -func ListComponents(writer io.Writer, persistentFlags utils.PersistentCommandFlags, componentFlags utils.ComponentCommandFlags, whereFilters []common.WhereFilter) (err error) { +func ListComponents(writer io.Writer, persistentFlags utils.PersistentCommandFlags, whereFilters []common.WhereFilter) (err error) { getLogger().Enter() defer getLogger().Exit() @@ -238,7 +174,7 @@ func ListComponents(writer io.Writer, persistentFlags utils.PersistentCommandFla // Hash all licenses within input file getLogger().Infof("Scanning document for licenses...") - err = loadDocumentComponents(document, componentFlags.Types, whereFilters) + err = loadDocumentComponents(document, whereFilters) if err != nil { return @@ -263,7 +199,7 @@ func ListComponents(writer io.Writer, persistentFlags utils.PersistentCommandFla return } -func loadDocumentComponents(document *schema.BOM, componentTypes string, whereFilters []common.WhereFilter) (err error) { +func loadDocumentComponents(document *schema.BOM, whereFilters []common.WhereFilter) (err error) { getLogger().Enter() defer getLogger().Exit(err) @@ -295,8 +231,8 @@ func sortComponents(entries []multimap.Entry) { sort.Slice(entries, func(i, j int) bool { resource1 := (entries[i].Value).(schema.CDXResourceInfo) resource2 := (entries[j].Value).(schema.CDXResourceInfo) - if resource1.Type != resource2.Type { - return resource1.Type < resource2.Type + if resource1.ResourceType != resource2.ResourceType { + return resource1.ResourceType < resource2.ResourceType } return resource1.Name < resource2.Name }) diff --git a/cmd/license_list.go b/cmd/license_list.go index 692cd6b0..28bb3992 100644 --- a/cmd/license_list.go +++ b/cmd/license_list.go @@ -93,9 +93,6 @@ var LICENSE_LIST_SUPPORTED_FORMATS = MSG_SUPPORTED_OUTPUT_FORMATS_HELP + strings.Join([]string{FORMAT_JSON, FORMAT_CSV, FORMAT_MARKDOWN}, ", ") + " (default: json)" -// Title row names for formatted lists (reports) -//var LICENSE_LIST_TITLES_LICENSE_CHOICE = []string{"License.Id", "License.Name", "License.Url", "Expression", "License.Text.ContentType", "License.Text.Encoding", "License.Text.Content"} - // WARNING: Cobra will not recognize a subcommand if its `command.Use` is not a single // word string that matches one of the `command.ValidArgs` set on the parent command func NewCommandList() *cobra.Command { diff --git a/cmd/report.go b/cmd/report.go index f6206c31..0841139b 100644 --- a/cmd/report.go +++ b/cmd/report.go @@ -241,10 +241,13 @@ func prepareReportLineData(structIn interface{}, formatData []ColumnFormatData, data, dataFound = mapStruct[columnData.DataKey] if !dataFound { - err = getLogger().Errorf("data not found in structure: key: `%s`", columnData.DataKey) - return + // TODO: change back? + getLogger().Errorf("data not found in structure: key: `%s`", columnData.DataKey) + data = "" + //return } + //fmt.Printf("data: `%v` (%T)\n", data, data) switch typedData := data.(type) { case string: // replace line feeds with spaces in description diff --git a/cmd/resource.go b/cmd/resource.go index 242dba86..d9c3b56d 100644 --- a/cmd/resource.go +++ b/cmd/resource.go @@ -42,14 +42,14 @@ var VALID_SUBCOMMANDS_RESOURCE = []string{SUBCOMMAND_RESOURCE_LIST} // filter keys // Note: these string values MUST match annotations for the ResourceInfo struct fields const ( - RESOURCE_FILTER_KEY_TYPE = "type" - RESOURCE_FILTER_KEY_NAME = "name" - RESOURCE_FILTER_KEY_VERSION = "version" - RESOURCE_FILTER_KEY_BOMREF = "bom-ref" + RESOURCE_FILTER_KEY_RESOURCE_TYPE = "resource-type" + RESOURCE_FILTER_KEY_NAME = "name" + RESOURCE_FILTER_KEY_VERSION = "version" + RESOURCE_FILTER_KEY_BOMREF = "bom-ref" ) var RESOURCE_LIST_ROW_DATA = []ColumnFormatData{ - *NewColumnFormatData(RESOURCE_FILTER_KEY_TYPE, DEFAULT_COLUMN_TRUNCATE_LENGTH, REPORT_SUMMARY_DATA_TRUE, false), + *NewColumnFormatData(RESOURCE_FILTER_KEY_RESOURCE_TYPE, DEFAULT_COLUMN_TRUNCATE_LENGTH, REPORT_SUMMARY_DATA_TRUE, false), *NewColumnFormatData(RESOURCE_FILTER_KEY_NAME, DEFAULT_COLUMN_TRUNCATE_LENGTH, REPORT_SUMMARY_DATA_TRUE, false), *NewColumnFormatData(RESOURCE_FILTER_KEY_VERSION, DEFAULT_COLUMN_TRUNCATE_LENGTH, REPORT_SUMMARY_DATA_TRUE, false), *NewColumnFormatData(RESOURCE_FILTER_KEY_BOMREF, DEFAULT_COLUMN_TRUNCATE_LENGTH, REPORT_SUMMARY_DATA_TRUE, REPORT_REPLACE_LINE_FEEDS_TRUE), @@ -255,8 +255,8 @@ func sortResources(entries []multimap.Entry) { sort.Slice(entries, func(i, j int) bool { resource1 := (entries[i].Value).(schema.CDXResourceInfo) resource2 := (entries[j].Value).(schema.CDXResourceInfo) - if resource1.Type != resource2.Type { - return resource1.Type < resource2.Type + if resource1.ResourceType != resource2.ResourceType { + return resource1.ResourceType < resource2.ResourceType } return resource1.Name < resource2.Name }) diff --git a/cmd/stats.go b/cmd/stats.go index a5759881..b456c97a 100644 --- a/cmd/stats.go +++ b/cmd/stats.go @@ -225,8 +225,8 @@ func DisplayStatsText(bom *schema.BOM, writer io.Writer) { sort.Slice(entries, func(i, j int) bool { resource1 := (entries[i].Value).(schema.CDXResourceInfo) resource2 := (entries[j].Value).(schema.CDXResourceInfo) - if resource1.Type != resource2.Type { - return resource1.Type < resource2.Type + if resource1.ResourceType != resource2.ResourceType { + return resource1.ResourceType < resource2.ResourceType } return resource1.Name < resource2.Name @@ -240,7 +240,7 @@ func DisplayStatsText(bom *schema.BOM, writer io.Writer) { // Format line and write to output fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", - resourceInfo.Type, + resourceInfo.ResourceType, resourceInfo.Name, resourceInfo.Version, resourceInfo.BOMRef) diff --git a/schema/bom_hash.go b/schema/bom_hash.go index 0c8a72fc..a4a6d226 100644 --- a/schema/bom_hash.go +++ b/schema/bom_hash.go @@ -83,7 +83,8 @@ func (bom *BOM) HashmapComponents(components []CDXComponent, whereFilters []comm func (bom *BOM) HashmapComponent(cdxComponent CDXComponent, whereFilters []common.WhereFilter, root bool) (hashed bool, err error) { getLogger().Enter() defer getLogger().Exit(err) - var resourceInfo CDXResourceInfo + //var componentInfo CDXResourceInfo + var componentInfo CDXComponentInfo if reflect.DeepEqual(cdxComponent, CDXComponent{}) { getLogger().Warning("empty component object found") @@ -98,40 +99,41 @@ func (bom *BOM) HashmapComponent(cdxComponent CDXComponent, whereFilters []commo getLogger().Warningf("component named `%s` missing `version`", cdxComponent.Name) } - if cdxComponent.BOMRef != nil && *cdxComponent.BOMRef == "" { + //if cdxComponent.BOMRef != nil && *cdxComponent.BOMRef == "" { + if cdxComponent.BOMRef == nil || *cdxComponent.BOMRef == "" { getLogger().Warningf("component named `%s` missing `bom-ref`", cdxComponent.Name) } // hash any component w/o a license using special key name - resourceInfo.IsRoot = root - resourceInfo.Type = RESOURCE_TYPE_COMPONENT - resourceInfo.Component = cdxComponent - resourceInfo.Name = cdxComponent.Name + componentInfo.IsRoot = root + componentInfo.ResourceType = RESOURCE_TYPE_COMPONENT + componentInfo.Component = cdxComponent + componentInfo.Name = cdxComponent.Name if cdxComponent.BOMRef != nil { ref := *cdxComponent.BOMRef - resourceInfo.BOMRef = ref.String() + componentInfo.BOMRef = ref.String() } - resourceInfo.Version = cdxComponent.Version + componentInfo.Version = cdxComponent.Version if cdxComponent.Supplier != nil { - resourceInfo.SupplierProvider = cdxComponent.Supplier + componentInfo.SupplierProvider = cdxComponent.Supplier } - resourceInfo.Properties = cdxComponent.Properties + componentInfo.Properties = cdxComponent.Properties var match bool = true if len(whereFilters) > 0 { - mapResourceInfo, _ := utils.MarshalStructToJsonMap(resourceInfo) + mapResourceInfo, _ := utils.MarshalStructToJsonMap(componentInfo) match, _ = whereFilterMatch(mapResourceInfo, whereFilters) } if match { hashed = true - bom.ComponentMap.Put(resourceInfo.BOMRef, resourceInfo) - bom.ResourceMap.Put(resourceInfo.BOMRef, resourceInfo) + bom.ComponentMap.Put(componentInfo.BOMRef, componentInfo) + bom.ResourceMap.Put(componentInfo.BOMRef, componentInfo.CDXResourceInfo) - getLogger().Tracef("Put: %s (`%s`), `%s`)", - resourceInfo.Name, - resourceInfo.Version, - resourceInfo.BOMRef) + getLogger().Infof("Put: %s (`%s`), `%s`)", + componentInfo.Name, + componentInfo.Version, + componentInfo.BOMRef) } // Recursively hash licenses for all child components (i.e., hierarchical composition) @@ -196,12 +198,12 @@ func (bom *BOM) HashmapService(cdxService CDXService, whereFilters []common.Wher getLogger().Warningf("service named `%s` missing `version`", cdxService.Name) } - if cdxService.BOMRef == nil || *cdxService.BOMRef != "" { + if cdxService.BOMRef == nil || *cdxService.BOMRef == "" { getLogger().Warningf("service named `%s` missing `bom-ref`", cdxService.Name) } // hash any component w/o a license using special key name - resourceInfo.Type = RESOURCE_TYPE_SERVICE + resourceInfo.ResourceType = RESOURCE_TYPE_SERVICE resourceInfo.Service = cdxService resourceInfo.Name = cdxService.Name if cdxService.BOMRef != nil { @@ -226,7 +228,7 @@ func (bom *BOM) HashmapService(cdxService CDXService, whereFilters []common.Wher bom.ResourceMap.Put(resourceInfo.BOMRef, resourceInfo) getLogger().Tracef("Put: [`%s`] %s (`%s`), `%s`)", - resourceInfo.Type, + resourceInfo.ResourceType, resourceInfo.Name, resourceInfo.Version, resourceInfo.BOMRef, diff --git a/schema/cyclonedx.go b/schema/cyclonedx.go index 02e39269..a4ed1763 100644 --- a/schema/cyclonedx.go +++ b/schema/cyclonedx.go @@ -117,13 +117,13 @@ type CDXComponent struct { Components *[]CDXComponent `json:"components,omitempty"` Evidence *CDXComponentEvidence `json:"evidence,omitempty"` // v1.3: added Properties *[]CDXProperty `json:"properties,omitempty"` // v1.3: added - Modified bool `json:"modified,omitempty" cdx:"deprecated"` // v1.4: deprecated ReleaseNotes *[]CDXReleaseNotes `json:"releaseNotes,omitempty"` // v1.4: added Signature *JSFSignature `json:"signature,omitempty"` // v1.4: added ModelCard *CDXModelCard `json:"modelCard,omitempty"` // v1.5: added Data *[]CDXComponentData `json:"data,omitempty"` // v1.5: added Authors *[]CDXOrganizationalContact `json:"authors,omitempty"` // v1.6: added Tags *[]string `json:"tags,omitempty" cdx:"+1.6"` // v1.6: added + Modified bool `json:"modified,omitempty" cdx:"deprecated"` // v1.4: deprecated Author string `json:"author,omitempty"` // v1.6: deprecated } diff --git a/schema/cyclonedx_abstractions.go b/schema/cyclonedx_abstractions.go index 46471196..2fbac5e5 100644 --- a/schema/cyclonedx_abstractions.go +++ b/schema/cyclonedx_abstractions.go @@ -51,16 +51,37 @@ func IsValidResourceType(value string) bool { // the CDX types CDXComponent and CDXService. type CDXResourceInfo struct { IsRoot bool - Type string `json:"type"` - BOMRef string `json:"bom-ref"` + ResourceType string `json:"resource-type"` + Group string `json:"group"` Name string `json:"name"` Version string `json:"version"` + Description string `json:"description"` + BOMRef string `json:"bom-ref"` SupplierProvider *CDXOrganizationalEntity Properties *[]CDXProperty Component CDXComponent Service CDXService } +// ------------------- +// Components +// ------------------- + +// TODO: Supplier (*CDXOrganizationalEntity), Authors (*[]CDXOrganizationalContact) +// TODO: HasHashes, HasLicenses, HasPedigree, HasEvidence, HasComponents, HasReleaseNotes +// TODO: HasModelCard, HasData, HasTags, HasSignature (*JSFSignature) +// TODO: OmniborId (new), Swhid (new) +type CDXComponentInfo struct { + Type string `json:"type"` + Publisher string `json:"publisher,omitempty"` + Scope string `json:"scope,omitempty"` + Copyright string `json:"copyright,omitempty"` + Cpe string `json:"cpe,omitempty"` // See: https://nvd.nist.gov/products/cpe + Purl string `json:"purl,omitempty" scvs:"bom:resource:identifiers:purl"` // See: https://github.com/package-url/purl-spec + Swid *CDXSwid `json:"swid,omitempty"` + CDXResourceInfo +} + // ------------------- // Vulnerabilities // -------------------