From 771ffe9b55831125cd221d75c0004bd9fea95162 Mon Sep 17 00:00:00 2001 From: Christoph Hartmann Date: Thu, 15 Feb 2024 00:15:09 +0100 Subject: [PATCH] =?UTF-8?q?=E2=AD=90=EF=B8=8F=20add=20evidence=20of=20pack?= =?UTF-8?q?age=20files=20on=20disk=20into=20sbom?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/cnquery/cmd/sbom.go | 13 ++ sbom/cnquery_bom_test.go | 4 +- sbom/cyclonedx.go | 31 +++- sbom/cyclonedx_test.go | 4 +- sbom/formats.go | 2 +- sbom/report_collection.go | 16 +- sbom/sbom.go | 46 ++++-- sbom/sbom.mql.yaml | 2 +- sbom/sbom.pb.go | 241 ++++++++++++++++++++++------ sbom/sbom.proto | 18 +++ sbom/sbom_test.go | 23 ++- sbom/spdx_test.go | 2 +- sbom/testdata/alpine.json | 291 ++++++++++++++++++++++++++++++++-- sbom/{list.go => textlist.go} | 34 +++- 14 files changed, 635 insertions(+), 92 deletions(-) rename sbom/{list.go => textlist.go} (63%) diff --git a/apps/cnquery/cmd/sbom.go b/apps/cnquery/cmd/sbom.go index 7eefb8bdbd..195dabde5d 100644 --- a/apps/cnquery/cmd/sbom.go +++ b/apps/cnquery/cmd/sbom.go @@ -27,6 +27,7 @@ func init() { sbomCmd.Flags().String("asset-name", "", "User-override for the asset name") sbomCmd.Flags().StringToString("annotation", nil, "Add an annotation to the asset.") // user-added, editable sbomCmd.Flags().StringP("output", "o", "list", "Set output format: "+sbom.AllFormats()) + sbomCmd.Flags().Bool("with-evidences", false, "Display evidence for each component") } var sbomCmd = &cobra.Command{ @@ -50,6 +51,11 @@ Note this command is experimental and may change in the future. if err != nil { log.Fatal().Err(err).Msg("failed to bind output flag") } + + err = viper.BindPFlag("with-evidences", cmd.Flags().Lookup("with-evidences")) + if err != nil { + log.Fatal().Err(err).Msg("failed to bind with-evidences flag") + } }, // we have to initialize an empty run so it shows up as a runnable command in --help Run: func(cmd *cobra.Command, args []string) {}, @@ -100,6 +106,13 @@ var sbomCmdRun = func(cmd *cobra.Command, runtime *providers.Runtime, cliRes *pl log.Fatal().Err(err).Msg("failed to get exporter for output format: " + output) } + if viper.GetBool("with-evidences") { + x, ok := exporter.(*sbom.TextList) + if ok { + x.ApplyOptions(sbom.WithEvidences()) + } + } + for _, bom := range boms { output := bytes.Buffer{} err := exporter.Render(&output, &bom) diff --git a/sbom/cnquery_bom_test.go b/sbom/cnquery_bom_test.go index dba408a948..96712c6755 100644 --- a/sbom/cnquery_bom_test.go +++ b/sbom/cnquery_bom_test.go @@ -30,5 +30,7 @@ func TestSimpleBom(t *testing.T) { data := output.String() assert.Contains(t, data, "alpine-baselayout") - assert.Contains(t, data, "cpe:2.3:a:alpine-baselayout:alpine-baselayout:1683642107:x86_64:*:*:*:*:amd64:*") + assert.Contains(t, data, "cpe:2.3:a:alpine-baselayout:alpine-baselayout:1695795276:aarch64:*:*:*:*:*:*") + // check that package files are included + assert.Contains(t, data, "etc/profile.d/color_prompt.sh.disabled") } diff --git a/sbom/cyclonedx.go b/sbom/cyclonedx.go index 030dedb047..e61fc212b3 100644 --- a/sbom/cyclonedx.go +++ b/sbom/cyclonedx.go @@ -60,14 +60,41 @@ func (ccx *CycloneDX) convert(bom *Sbom) (*cyclonedx.BOM, error) { if len(pkg.Cpes) > 0 { cpe = pkg.Cpes[0] } + + fileLocations := []cyclonedx.EvidenceOccurrence{} + + // pkg.Location is deprecated, use pkg.Evidences instead + if pkg.Location != "" { + fileLocations = append(fileLocations, cyclonedx.EvidenceOccurrence{ + Location: pkg.Location, + }) + } + + if pkg.Evidences != nil { + for i := range pkg.Evidences { + e := pkg.Evidences[i] + if e.Type == EvidenceType_EVIDENCE_TYPE_FILE { + fileLocations = append(fileLocations, cyclonedx.EvidenceOccurrence{ + Location: e.Value, + }) + } + } + } + + var evidence *cyclonedx.Evidence + if len(fileLocations) > 0 { + evidence = &cyclonedx.Evidence{ + Occurrences: &fileLocations, + } + } + bomPkg := cyclonedx.Component{ Type: cyclonedx.ComponentTypeLibrary, Name: pkg.Name, Version: pkg.Version, PackageURL: pkg.Purl, CPE: cpe, - // TODO: use evidence location in 1.5 once available - Description: pkg.Location, + Evidence: evidence, } components = append(components, bomPkg) diff --git a/sbom/cyclonedx_test.go b/sbom/cyclonedx_test.go index ead1a333a9..cf3a2f1ce6 100644 --- a/sbom/cyclonedx_test.go +++ b/sbom/cyclonedx_test.go @@ -33,5 +33,7 @@ func TestCycloneDX(t *testing.T) { // os.WriteFile("./testdata/bom_cyclone.json", output.Bytes(), 0700) assert.Contains(t, data, "cyclonedx") assert.Contains(t, data, "alpine-baselayout") - assert.Contains(t, data, "cpe:2.3:a:alpine-baselayout:alpine-baselayout:1683642107:x86_64:*:*:*:*:amd64:*") + assert.Contains(t, data, "cpe:2.3:a:alpine-baselayout:alpine-baselayout:1695795276:aarch64:*:*:*:*:*:*") + // check that package files are included + assert.Contains(t, data, "etc/profile.d/color_prompt.sh.disabled") } diff --git a/sbom/formats.go b/sbom/formats.go index 6aaa88bf93..23dadae083 100644 --- a/sbom/formats.go +++ b/sbom/formats.go @@ -50,6 +50,6 @@ func NewExporter(fomat string) Exporter { case FormatList: fallthrough default: - return &CliList{} + return &TextList{} } } diff --git a/sbom/report_collection.go b/sbom/report_collection.go index ab20e4748e..51f96cf179 100644 --- a/sbom/report_collection.go +++ b/sbom/report_collection.go @@ -39,13 +39,17 @@ type BomAsset struct { } type BomPackage struct { - Name string `json:"name,omitempty"` - Version string `json:"version,omitempty"` - Purl string `json:"purl,omitempty"` - CPEs []string `json:"cpes.map,omitempty"` - FilePath string `json:"file.path,omitempty"` - Format string `json:"format,omitempty"` + Name string `json:"name,omitempty"` + Version string `json:"version,omitempty"` + Purl string `json:"purl,omitempty"` + CPEs []string `json:"cpes.map,omitempty"` + // used by python packages + FilePath string `json:"file.path,omitempty"` + // used by os packages + FilePaths []string `json:"files.map,omitempty"` + Format string `json:"format,omitempty"` } + type BomReport struct { Asset *BomAsset `json:"asset,omitempty"` Packages []BomPackage `json:"packages.list,omitempty"` diff --git a/sbom/sbom.go b/sbom/sbom.go index 3f341c7113..42707f3fc5 100644 --- a/sbom/sbom.go +++ b/sbom/sbom.go @@ -80,25 +80,51 @@ func GenerateBom(r *ReportCollectionJson) ([]Sbom, error) { } if rb.Packages != nil { for _, pkg := range rb.Packages { - bom.Packages = append(bom.Packages, &Package{ + bomPkg := &Package{ Name: pkg.Name, Version: pkg.Version, Purl: pkg.Purl, Cpes: pkg.CPEs, Type: pkg.Format, - }) + } + + for _, filepath := range pkg.FilePaths { + bomPkg.Evidences = append(bomPkg.Evidences, &Evidence{ + Type: EvidenceType_EVIDENCE_TYPE_FILE, + Value: filepath, + }) + } + + bom.Packages = append(bom.Packages, bomPkg) } } if rb.PythonPackages != nil { for _, pkg := range rb.PythonPackages { - bom.Packages = append(bom.Packages, &Package{ - Name: pkg.Name, - Version: pkg.Version, - Purl: pkg.Purl, - Cpes: pkg.CPEs, - Location: pkg.FilePath, - Type: "pypi", - }) + bomPkg := &Package{ + Name: pkg.Name, + Version: pkg.Version, + Purl: pkg.Purl, + Cpes: pkg.CPEs, + Type: "pypi", + } + + // deprecated path, all files are now in the FilePaths field + // TODO: update once the pythong resource returns multiple results + if pkg.FilePath != "" { + bomPkg.Evidences = append(bomPkg.Evidences, &Evidence{ + Type: EvidenceType_EVIDENCE_TYPE_FILE, + Value: pkg.FilePath, + }) + } + + for _, filepath := range pkg.FilePaths { + bomPkg.Evidences = append(bomPkg.Evidences, &Evidence{ + Type: EvidenceType_EVIDENCE_TYPE_FILE, + Value: filepath, + }) + } + + bom.Packages = append(bom.Packages, bomPkg) } } } diff --git a/sbom/sbom.mql.yaml b/sbom/sbom.mql.yaml index e9ac051aaf..fb4419111d 100644 --- a/sbom/sbom.mql.yaml +++ b/sbom/sbom.mql.yaml @@ -12,7 +12,7 @@ packs: mql: asset { name platform version arch ids labels cpes.map(uri) } - uid: mondoo-sbom-packages title: Retrieve list of installed packages - mql: packages { name version purl cpes.map(uri) format } + mql: packages { name version purl cpes.map(uri) format files.map(path) } - uid: mondoo-sbom-python-packages title: Retrieve list of installed Python packages mql: python.packages { name version purl cpes.map(uri) file.path } \ No newline at end of file diff --git a/sbom/sbom.pb.go b/sbom/sbom.pb.go index b651bab4fb..a73e6682e7 100644 --- a/sbom/sbom.pb.go +++ b/sbom/sbom.pb.go @@ -169,6 +169,52 @@ func (ExternalIDType) EnumDescriptor() ([]byte, []int) { return file_sbom_proto_rawDescGZIP(), []int{1} } +type EvidenceType int32 + +const ( + EvidenceType_EVIDENCE_TYPE_UNSPECIFIED EvidenceType = 0 + EvidenceType_EVIDENCE_TYPE_FILE EvidenceType = 1 +) + +// Enum value maps for EvidenceType. +var ( + EvidenceType_name = map[int32]string{ + 0: "EVIDENCE_TYPE_UNSPECIFIED", + 1: "EVIDENCE_TYPE_FILE", + } + EvidenceType_value = map[string]int32{ + "EVIDENCE_TYPE_UNSPECIFIED": 0, + "EVIDENCE_TYPE_FILE": 1, + } +) + +func (x EvidenceType) Enum() *EvidenceType { + p := new(EvidenceType) + *p = x + return p +} + +func (x EvidenceType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (EvidenceType) Descriptor() protoreflect.EnumDescriptor { + return file_sbom_proto_enumTypes[2].Descriptor() +} + +func (EvidenceType) Type() protoreflect.EnumType { + return &file_sbom_proto_enumTypes[2] +} + +func (x EvidenceType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use EvidenceType.Descriptor instead. +func (EvidenceType) EnumDescriptor() ([]byte, []int) { + return file_sbom_proto_rawDescGZIP(), []int{2} +} + // Sbom (Software Bill of Materials) represents a comprehensive inventory of // software packages. It is a structured list of all software components that // are part of a given asset, such as a virtual machine or container. The Sbom @@ -657,11 +703,15 @@ type Package struct { // software package metadata. Purl string `protobuf:"bytes,5,opt,name=purl,proto3" json:"purl,omitempty"` // location on disk + // Deprecated: use evidence instead Location string `protobuf:"bytes,6,opt,name=location,proto3" json:"location,omitempty"` // 'type' indicates the type of package, such as a rpm, dpkg, or gem. Type string `protobuf:"bytes,7,opt,name=type,proto3" json:"type,omitempty"` // description of the package Description string `protobuf:"bytes,20,opt,name=description,proto3" json:"description,omitempty"` + // 'evidence' is a collection of evidence that supports the presence of the + // package in the asset. This evidence could include eg. file paths + Evidences []*Evidence `protobuf:"bytes,21,rep,name=evidences,proto3" json:"evidences,omitempty"` } func (x *Package) Reset() { @@ -752,6 +802,72 @@ func (x *Package) GetDescription() string { return "" } +func (x *Package) GetEvidences() []*Evidence { + if x != nil { + return x.Evidences + } + return nil +} + +type Evidence struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // 'type' indicates the type of evidence, such as a file path. + Type EvidenceType `protobuf:"varint,1,opt,name=type,proto3,enum=mondoo.sbom.v1.EvidenceType" json:"type,omitempty"` + // 'value' is the actual evidence that supports the presence of the package in + // the asset. The format and interpretation of this value depend on the + // 'type'. For example, it could be a file path for file evidence. + Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *Evidence) Reset() { + *x = Evidence{} + if protoimpl.UnsafeEnabled { + mi := &file_sbom_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Evidence) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Evidence) ProtoMessage() {} + +func (x *Evidence) ProtoReflect() protoreflect.Message { + mi := &file_sbom_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Evidence.ProtoReflect.Descriptor instead. +func (*Evidence) Descriptor() ([]byte, []int) { + return file_sbom_proto_rawDescGZIP(), []int{6} +} + +func (x *Evidence) GetType() EvidenceType { + if x != nil { + return x.Type + } + return EvidenceType_EVIDENCE_TYPE_UNSPECIFIED +} + +func (x *Evidence) GetValue() string { + if x != nil { + return x.Value + } + return "" +} + var File_sbom_proto protoreflect.FileDescriptor var file_sbom_proto_rawDesc = []byte{ @@ -822,7 +938,7 @@ var file_sbom_proto_rawDesc = []byte{ 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xd5, 0x01, 0x0a, 0x07, 0x50, 0x61, 0x63, 0x6b, 0x61, + 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x8d, 0x02, 0x0a, 0x07, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, @@ -835,27 +951,40 @@ var file_sbom_proto_rawDesc = []byte{ 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x14, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x2a, 0x7d, - 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x54, 0x41, 0x54, - 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, - 0x12, 0x14, 0x0a, 0x10, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, - 0x45, 0x44, 0x45, 0x44, 0x10, 0x01, 0x12, 0x1e, 0x0a, 0x1a, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, - 0x5f, 0x50, 0x41, 0x52, 0x54, 0x49, 0x41, 0x4c, 0x4c, 0x59, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, - 0x45, 0x44, 0x45, 0x44, 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, - 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, - 0x54, 0x55, 0x53, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x45, 0x44, 0x10, 0x04, 0x2a, 0x90, 0x01, - 0x0a, 0x0e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x49, 0x44, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x20, 0x0a, 0x1c, 0x45, 0x58, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x5f, 0x49, 0x44, 0x5f, - 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, - 0x10, 0x00, 0x12, 0x20, 0x0a, 0x1c, 0x45, 0x58, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x5f, 0x49, - 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x57, 0x53, 0x5f, 0x41, 0x43, 0x43, 0x4f, 0x55, - 0x4e, 0x54, 0x10, 0x01, 0x12, 0x1c, 0x0a, 0x18, 0x45, 0x58, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, - 0x5f, 0x49, 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x57, 0x53, 0x5f, 0x41, 0x52, 0x4e, - 0x10, 0x02, 0x12, 0x1c, 0x0a, 0x18, 0x45, 0x58, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x5f, 0x49, - 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x57, 0x53, 0x5f, 0x4f, 0x52, 0x47, 0x10, 0x03, - 0x42, 0x1c, 0x5a, 0x1a, 0x67, 0x6f, 0x2e, 0x6d, 0x6f, 0x6e, 0x64, 0x6f, 0x6f, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x63, 0x6e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2f, 0x73, 0x62, 0x6f, 0x6d, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x36, + 0x0a, 0x09, 0x65, 0x76, 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x15, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x18, 0x2e, 0x6d, 0x6f, 0x6e, 0x64, 0x6f, 0x6f, 0x2e, 0x73, 0x62, 0x6f, 0x6d, 0x2e, + 0x76, 0x31, 0x2e, 0x45, 0x76, 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x09, 0x65, 0x76, 0x69, + 0x64, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x22, 0x52, 0x0a, 0x08, 0x45, 0x76, 0x69, 0x64, 0x65, 0x6e, + 0x63, 0x65, 0x12, 0x30, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x1c, 0x2e, 0x6d, 0x6f, 0x6e, 0x64, 0x6f, 0x6f, 0x2e, 0x73, 0x62, 0x6f, 0x6d, 0x2e, 0x76, + 0x31, 0x2e, 0x45, 0x76, 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2a, 0x7d, 0x0a, 0x06, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, + 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, + 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x45, 0x44, 0x45, 0x44, + 0x10, 0x01, 0x12, 0x1e, 0x0a, 0x1a, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x50, 0x41, 0x52, + 0x54, 0x49, 0x41, 0x4c, 0x4c, 0x59, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x45, 0x44, 0x45, 0x44, + 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x46, 0x41, 0x49, + 0x4c, 0x45, 0x44, 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, + 0x53, 0x54, 0x41, 0x52, 0x54, 0x45, 0x44, 0x10, 0x04, 0x2a, 0x90, 0x01, 0x0a, 0x0e, 0x45, 0x78, + 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x49, 0x44, 0x54, 0x79, 0x70, 0x65, 0x12, 0x20, 0x0a, 0x1c, + 0x45, 0x58, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x5f, 0x49, 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45, + 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x20, + 0x0a, 0x1c, 0x45, 0x58, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x5f, 0x49, 0x44, 0x5f, 0x54, 0x59, + 0x50, 0x45, 0x5f, 0x41, 0x57, 0x53, 0x5f, 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x10, 0x01, + 0x12, 0x1c, 0x0a, 0x18, 0x45, 0x58, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x5f, 0x49, 0x44, 0x5f, + 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x57, 0x53, 0x5f, 0x41, 0x52, 0x4e, 0x10, 0x02, 0x12, 0x1c, + 0x0a, 0x18, 0x45, 0x58, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x5f, 0x49, 0x44, 0x5f, 0x54, 0x59, + 0x50, 0x45, 0x5f, 0x41, 0x57, 0x53, 0x5f, 0x4f, 0x52, 0x47, 0x10, 0x03, 0x2a, 0x45, 0x0a, 0x0c, + 0x45, 0x76, 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x19, + 0x45, 0x56, 0x49, 0x44, 0x45, 0x4e, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, + 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x45, + 0x56, 0x49, 0x44, 0x45, 0x4e, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x46, 0x49, 0x4c, + 0x45, 0x10, 0x01, 0x42, 0x1c, 0x5a, 0x1a, 0x67, 0x6f, 0x2e, 0x6d, 0x6f, 0x6e, 0x64, 0x6f, 0x6f, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2f, 0x73, 0x62, 0x6f, + 0x6d, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -870,35 +999,39 @@ func file_sbom_proto_rawDescGZIP() []byte { return file_sbom_proto_rawDescData } -var file_sbom_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_sbom_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_sbom_proto_enumTypes = make([]protoimpl.EnumInfo, 3) +var file_sbom_proto_msgTypes = make([]protoimpl.MessageInfo, 9) var file_sbom_proto_goTypes = []interface{}{ (Status)(0), // 0: mondoo.sbom.v1.Status (ExternalIDType)(0), // 1: mondoo.sbom.v1.ExternalIDType - (*Sbom)(nil), // 2: mondoo.sbom.v1.Sbom - (*Generator)(nil), // 3: mondoo.sbom.v1.Generator - (*ExternalID)(nil), // 4: mondoo.sbom.v1.ExternalID - (*Asset)(nil), // 5: mondoo.sbom.v1.Asset - (*Platform)(nil), // 6: mondoo.sbom.v1.Platform - (*Package)(nil), // 7: mondoo.sbom.v1.Package - nil, // 8: mondoo.sbom.v1.Asset.LabelsEntry - nil, // 9: mondoo.sbom.v1.Platform.LabelsEntry + (EvidenceType)(0), // 2: mondoo.sbom.v1.EvidenceType + (*Sbom)(nil), // 3: mondoo.sbom.v1.Sbom + (*Generator)(nil), // 4: mondoo.sbom.v1.Generator + (*ExternalID)(nil), // 5: mondoo.sbom.v1.ExternalID + (*Asset)(nil), // 6: mondoo.sbom.v1.Asset + (*Platform)(nil), // 7: mondoo.sbom.v1.Platform + (*Package)(nil), // 8: mondoo.sbom.v1.Package + (*Evidence)(nil), // 9: mondoo.sbom.v1.Evidence + nil, // 10: mondoo.sbom.v1.Asset.LabelsEntry + nil, // 11: mondoo.sbom.v1.Platform.LabelsEntry } var file_sbom_proto_depIdxs = []int32{ - 3, // 0: mondoo.sbom.v1.Sbom.generator:type_name -> mondoo.sbom.v1.Generator - 0, // 1: mondoo.sbom.v1.Sbom.status:type_name -> mondoo.sbom.v1.Status - 5, // 2: mondoo.sbom.v1.Sbom.asset:type_name -> mondoo.sbom.v1.Asset - 7, // 3: mondoo.sbom.v1.Sbom.packages:type_name -> mondoo.sbom.v1.Package - 1, // 4: mondoo.sbom.v1.ExternalID.type:type_name -> mondoo.sbom.v1.ExternalIDType - 4, // 5: mondoo.sbom.v1.Asset.external_ids:type_name -> mondoo.sbom.v1.ExternalID - 6, // 6: mondoo.sbom.v1.Asset.platform:type_name -> mondoo.sbom.v1.Platform - 8, // 7: mondoo.sbom.v1.Asset.labels:type_name -> mondoo.sbom.v1.Asset.LabelsEntry - 9, // 8: mondoo.sbom.v1.Platform.labels:type_name -> mondoo.sbom.v1.Platform.LabelsEntry - 9, // [9:9] is the sub-list for method output_type - 9, // [9:9] is the sub-list for method input_type - 9, // [9:9] is the sub-list for extension type_name - 9, // [9:9] is the sub-list for extension extendee - 0, // [0:9] is the sub-list for field type_name + 4, // 0: mondoo.sbom.v1.Sbom.generator:type_name -> mondoo.sbom.v1.Generator + 0, // 1: mondoo.sbom.v1.Sbom.status:type_name -> mondoo.sbom.v1.Status + 6, // 2: mondoo.sbom.v1.Sbom.asset:type_name -> mondoo.sbom.v1.Asset + 8, // 3: mondoo.sbom.v1.Sbom.packages:type_name -> mondoo.sbom.v1.Package + 1, // 4: mondoo.sbom.v1.ExternalID.type:type_name -> mondoo.sbom.v1.ExternalIDType + 5, // 5: mondoo.sbom.v1.Asset.external_ids:type_name -> mondoo.sbom.v1.ExternalID + 7, // 6: mondoo.sbom.v1.Asset.platform:type_name -> mondoo.sbom.v1.Platform + 10, // 7: mondoo.sbom.v1.Asset.labels:type_name -> mondoo.sbom.v1.Asset.LabelsEntry + 11, // 8: mondoo.sbom.v1.Platform.labels:type_name -> mondoo.sbom.v1.Platform.LabelsEntry + 9, // 9: mondoo.sbom.v1.Package.evidences:type_name -> mondoo.sbom.v1.Evidence + 2, // 10: mondoo.sbom.v1.Evidence.type:type_name -> mondoo.sbom.v1.EvidenceType + 11, // [11:11] is the sub-list for method output_type + 11, // [11:11] is the sub-list for method input_type + 11, // [11:11] is the sub-list for extension type_name + 11, // [11:11] is the sub-list for extension extendee + 0, // [0:11] is the sub-list for field type_name } func init() { file_sbom_proto_init() } @@ -979,14 +1112,26 @@ func file_sbom_proto_init() { return nil } } + file_sbom_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Evidence); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_sbom_proto_rawDesc, - NumEnums: 2, - NumMessages: 8, + NumEnums: 3, + NumMessages: 9, NumExtensions: 0, NumServices: 0, }, diff --git a/sbom/sbom.proto b/sbom/sbom.proto index 36d23367b6..7e1933b252 100644 --- a/sbom/sbom.proto +++ b/sbom/sbom.proto @@ -207,9 +207,27 @@ message Package { // software package metadata. string purl = 5; // location on disk + // Deprecated: use evidence instead string location = 6; // 'type' indicates the type of package, such as a rpm, dpkg, or gem. string type = 7; // description of the package string description = 20; + // 'evidence' is a collection of evidence that supports the presence of the + // package in the asset. This evidence could include eg. file paths + repeated Evidence evidences = 21; +} + +enum EvidenceType { + EVIDENCE_TYPE_UNSPECIFIED = 0; + EVIDENCE_TYPE_FILE = 1; +} + +message Evidence { + // 'type' indicates the type of evidence, such as a file path. + EvidenceType type = 1; + // 'value' is the actual evidence that supports the presence of the package in + // the asset. The format and interpretation of this value depend on the + // 'type'. For example, it could be a file path for file evidence. + string value = 2; } \ No newline at end of file diff --git a/sbom/sbom_test.go b/sbom/sbom_test.go index 3a6a62dc3f..61a78fe637 100644 --- a/sbom/sbom_test.go +++ b/sbom/sbom_test.go @@ -19,11 +19,26 @@ func TestSbomParsing(t *testing.T) { // store bom in different formats selectedBom := sboms[0] - assert.Equal(t, "alpine:3.18", selectedBom.Asset.Name) - assert.Equal(t, "amd64", selectedBom.Asset.Platform.Arch) + assert.Equal(t, "alpine:latest", selectedBom.Asset.Name) + assert.Equal(t, "aarch64", selectedBom.Asset.Platform.Arch) assert.Equal(t, "alpine", selectedBom.Asset.Platform.Name) - assert.Equal(t, "3.18.4", selectedBom.Asset.Platform.Version) - assert.Equal(t, []string{"//platformid.api.mondoo.app/runtime/docker/images/e6b39dab7a69cfea9941378c0dbcc21b314c34eb22f5b9032c2023f6398e97b1"}, selectedBom.Asset.PlatformIds) + assert.Equal(t, "3.19.0", selectedBom.Asset.Platform.Version) + assert.Equal(t, []string{"//platformid.api.mondoo.app/runtime/docker/images/1dc785547989b0db1c3cd9949c57574393e69bea98bfe044b0588e24721aa402"}, selectedBom.Asset.PlatformIds) + + var pkg *Package + for i := range selectedBom.Packages { + if selectedBom.Packages[i].Name == "alpine-baselayout" { + pkg = selectedBom.Packages[i] + break + } + } + require.NotNil(t, pkg) + assert.Equal(t, "alpine-baselayout", pkg.Name) + assert.Contains(t, pkg.Evidences, &Evidence{ + Type: EvidenceType_EVIDENCE_TYPE_FILE, + Value: "etc/profile.d/color_prompt.sh.disabled", + }) + } func TestArnGeneration(t *testing.T) { diff --git a/sbom/spdx_test.go b/sbom/spdx_test.go index a5c5806cee..059d768b8d 100644 --- a/sbom/spdx_test.go +++ b/sbom/spdx_test.go @@ -32,5 +32,5 @@ func TestSpdx(t *testing.T) { res := output.String() assert.Contains(t, res, "SPDX-2.3") assert.Contains(t, res, "\"name\": \"alpine-baselayout\",") - assert.Contains(t, res, "\"cpe:2.3:a:alpine-baselayout:alpine-baselayout:1683642107:x86_64:*:*:*:*:amd64:*\"") + assert.Contains(t, res, "\"cpe:2.3:a:alpine-baselayout:alpine-baselayout:1695795276:aarch64:*:*:*:*:*:*\"") } diff --git a/sbom/testdata/alpine.json b/sbom/testdata/alpine.json index dce98f892d..12a8125210 100644 --- a/sbom/testdata/alpine.json +++ b/sbom/testdata/alpine.json @@ -1,43 +1,304 @@ { "assets": { - "//explorer.api.mondoo.com/assets/2YchtfothFBnHwN5M6amkNa5r2Q": { - "mrn": "//explorer.api.mondoo.com/assets/2YchtfothFBnHwN5M6amkNa5r2Q", - "name": "alpine:3.18" + "//explorer.api.mondoo.com/assets/2cNOBxIv7117UjcqsDBvKUgwkKw": { + "mrn": "//explorer.api.mondoo.com/assets/2cNOBxIv7117UjcqsDBvKUgwkKw", + "name": "alpine:latest" } }, "data": { - "//explorer.api.mondoo.com/assets/2YchtfothFBnHwN5M6amkNa5r2Q": { + "//explorer.api.mondoo.com/assets/2cNOBxIv7117UjcqsDBvKUgwkKw": { "//local.cnquery.io/run/local-execution/queries/mondoo-sbom-asset": { "asset": { - "version": "3.18.4", - "arch": "amd64", + "version": "3.19.0", + "arch": "aarch64", "labels": { "distro-id": "alpine" }, - "cpes.map": [], "platform": "alpine", "ids": [ - "//platformid.api.mondoo.app/runtime/docker/images/e6b39dab7a69cfea9941378c0dbcc21b314c34eb22f5b9032c2023f6398e97b1" + "//platformid.api.mondoo.app/runtime/docker/images/1dc785547989b0db1c3cd9949c57574393e69bea98bfe044b0588e24721aa402" ], - "name": "alpine:3.18" + "name": "alpine:latest", + "cpes.map": [ + "cpe:2.3:o:alpinelinux:alpine_linux:3.19.0:*:*:*:*:*:*:*" + ] } }, "//local.cnquery.io/run/local-execution/queries/mondoo-sbom-packages": { "packages.list": [ { - "purl": "pkg:apk/alpine/alpine-baselayout@1683642107%3A3.4.3-r1?arch=x86_64\u0026distro=alpine-3.18.4\u0026epoch=1683642107", - "version": "1683642107:3.4.3-r1", + "purl": "pkg:apk/alpine/alpine-baselayout@1695795276%3A3.4.3-r2?arch=aarch64\u0026distro=alpine-3.19.0\u0026epoch=1695795276", + "version": "1695795276:3.4.3-r2", "name": "alpine-baselayout", + "format": "apk", "cpes.map": [ - "cpe:2.3:a:alpine-baselayout:alpine-baselayout:1683642107:x86_64:*:*:*:*:amd64:*" + "cpe:2.3:a:alpine-baselayout:alpine-baselayout:1695795276:aarch64:*:*:*:*:*:*" + ], + "files.map": [ + "etc/motd", + "etc/crontabs/root", + "etc/modprobe.d/aliases.conf", + "etc/modprobe.d/blacklist.conf", + "etc/modprobe.d/i386.conf", + "etc/modprobe.d/kms.conf", + "etc/profile.d/20locale.sh", + "etc/profile.d/README", + "etc/profile.d/color_prompt.sh.disabled", + "lib/sysctl.d/00-alpine.conf", + "var/run", + "var/spool/mail", + "var/spool/cron/crontabs" ] }, { - "purl": "pkg:apk/alpine/zlib@1681228881%3A1.2.13-r1?arch=x86_64\u0026distro=alpine-3.18.4\u0026epoch=1681228881", - "version": "1681228881:1.2.13-r1", + "purl": "pkg:apk/alpine/alpine-baselayout-data@1695795276%3A3.4.3-r2?arch=aarch64\u0026distro=alpine-3.19.0\u0026epoch=1695795276", + "version": "1695795276:3.4.3-r2", + "name": "alpine-baselayout-data", + "format": "apk", + "cpes.map": [ + "cpe:2.3:a:alpine-baselayout-data:alpine-baselayout-data:1695795276:aarch64:*:*:*:*:*:*" + ], + "files.map": [ + "etc/fstab", + "etc/group", + "etc/hostname", + "etc/hosts", + "etc/inittab", + "etc/modules", + "etc/mtab", + "etc/nsswitch.conf", + "etc/passwd", + "etc/profile", + "etc/protocols", + "etc/services", + "etc/shadow", + "etc/shells", + "etc/sysctl.conf" + ] + }, + { + "purl": "pkg:apk/alpine/alpine-keys@1634579657%3A2.4-r1?arch=aarch64\u0026distro=alpine-3.19.0\u0026epoch=1634579657", + "version": "1634579657:2.4-r1", + "name": "alpine-keys", + "format": "apk", + "cpes.map": [ + "cpe:2.3:a:alpine-keys:alpine-keys:1634579657:aarch64:*:*:*:*:*:*" + ], + "files.map": [ + "etc/apk/keys/alpine-devel@lists.alpinelinux.org-524d27bb.rsa.pub", + "etc/apk/keys/alpine-devel@lists.alpinelinux.org-58199dcc.rsa.pub", + "etc/apk/keys/alpine-devel@lists.alpinelinux.org-616a9724.rsa.pub", + "etc/apk/keys/alpine-devel@lists.alpinelinux.org-616adfeb.rsa.pub", + "etc/apk/keys/alpine-devel@lists.alpinelinux.org-616ae350.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-5243ef4b.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-524d27bb.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-5261cecb.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-58199dcc.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-58cbb476.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-58e4f17d.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-5e69ca50.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-60ac2099.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-6165ee59.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-61666e3f.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-616a9724.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-616abc23.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-616ac3bc.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-616adfeb.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-616ae350.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-616db30d.rsa.pub", + "usr/share/apk/keys/aarch64/alpine-devel@lists.alpinelinux.org-58199dcc.rsa.pub", + "usr/share/apk/keys/aarch64/alpine-devel@lists.alpinelinux.org-616ae350.rsa.pub", + "usr/share/apk/keys/armhf/alpine-devel@lists.alpinelinux.org-524d27bb.rsa.pub", + "usr/share/apk/keys/armhf/alpine-devel@lists.alpinelinux.org-616a9724.rsa.pub", + "usr/share/apk/keys/armv7/alpine-devel@lists.alpinelinux.org-524d27bb.rsa.pub", + "usr/share/apk/keys/armv7/alpine-devel@lists.alpinelinux.org-616adfeb.rsa.pub", + "usr/share/apk/keys/mips64/alpine-devel@lists.alpinelinux.org-5e69ca50.rsa.pub", + "usr/share/apk/keys/ppc64le/alpine-devel@lists.alpinelinux.org-58cbb476.rsa.pub", + "usr/share/apk/keys/ppc64le/alpine-devel@lists.alpinelinux.org-616abc23.rsa.pub", + "usr/share/apk/keys/riscv64/alpine-devel@lists.alpinelinux.org-60ac2099.rsa.pub", + "usr/share/apk/keys/riscv64/alpine-devel@lists.alpinelinux.org-616db30d.rsa.pub", + "usr/share/apk/keys/s390x/alpine-devel@lists.alpinelinux.org-58e4f17d.rsa.pub", + "usr/share/apk/keys/s390x/alpine-devel@lists.alpinelinux.org-616ac3bc.rsa.pub", + "usr/share/apk/keys/x86/alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub", + "usr/share/apk/keys/x86/alpine-devel@lists.alpinelinux.org-5243ef4b.rsa.pub", + "usr/share/apk/keys/x86/alpine-devel@lists.alpinelinux.org-61666e3f.rsa.pub", + "usr/share/apk/keys/x86_64/alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub", + "usr/share/apk/keys/x86_64/alpine-devel@lists.alpinelinux.org-5261cecb.rsa.pub", + "usr/share/apk/keys/x86_64/alpine-devel@lists.alpinelinux.org-6165ee59.rsa.pub" + ] + }, + { + "purl": "pkg:apk/alpine/apk-tools@1684120357%3A2.14.0-r5?arch=aarch64\u0026distro=alpine-3.19.0\u0026epoch=1684120357", + "version": "1684120357:2.14.0-r5", + "name": "apk-tools", + "format": "apk", + "cpes.map": [ + "cpe:2.3:a:apk-tools:apk-tools:1684120357:aarch64:*:*:*:*:*:*" + ], + "files.map": [ + "lib/libapk.so.2.14.0", + "sbin/apk" + ] + }, + { + "purl": "pkg:apk/alpine/busybox@1699383189%3A1.36.1-r15?arch=aarch64\u0026distro=alpine-3.19.0\u0026epoch=1699383189", + "version": "1699383189:1.36.1-r15", + "name": "busybox", + "format": "apk", + "cpes.map": [ + "cpe:2.3:a:busybox:busybox:1699383189:aarch64:*:*:*:*:*:*" + ], + "files.map": [ + "bin/busybox", + "etc/securetty", + "etc/udhcpd.conf", + "etc/busybox-paths.d/busybox", + "etc/logrotate.d/acpid", + "etc/network/if-up.d/dad", + "etc/udhcpc/udhcpc.conf", + "usr/share/udhcpc/default.script" + ] + }, + { + "purl": "pkg:apk/alpine/busybox-binsh@1699383189%3A1.36.1-r15?arch=aarch64\u0026distro=alpine-3.19.0\u0026epoch=1699383189", + "version": "1699383189:1.36.1-r15", + "name": "busybox-binsh", + "format": "apk", + "cpes.map": [ + "cpe:2.3:a:busybox-binsh:busybox-binsh:1699383189:aarch64:*:*:*:*:*:*" + ], + "files.map": [ + "bin/sh" + ] + }, + { + "purl": "pkg:apk/alpine/ca-certificates-bundle@1683374901%3A20230506-r0?arch=aarch64\u0026distro=alpine-3.19.0\u0026epoch=1683374901", + "version": "1683374901:20230506-r0", + "name": "ca-certificates-bundle", + "format": "apk", + "cpes.map": [ + "cpe:2.3:a:ca-certificates-bundle:ca-certificates-bundle:1683374901:aarch64:*:*:*:*:*:*" + ], + "files.map": [ + "etc/ssl/cert.pem", + "etc/ssl/certs/ca-certificates.crt", + "etc/ssl1.1/cert.pem", + "etc/ssl1.1/certs" + ] + }, + { + "purl": "pkg:apk/alpine/libc-utils@1682166293%3A0.7.2-r5?arch=aarch64\u0026distro=alpine-3.19.0\u0026epoch=1682166293", + "version": "1682166293:0.7.2-r5", + "name": "libc-utils", + "format": "apk", + "cpes.map": [ + "cpe:2.3:a:libc-utils:libc-utils:1682166293:aarch64:*:*:*:*:*:*" + ], + "files.map": [] + }, + { + "purl": "pkg:apk/alpine/libcrypto3@1701101689%3A3.1.4-r2?arch=aarch64\u0026distro=alpine-3.19.0\u0026epoch=1701101689", + "version": "1701101689:3.1.4-r2", + "name": "libcrypto3", + "format": "apk", + "cpes.map": [ + "cpe:2.3:a:libcrypto3:libcrypto3:1701101689:aarch64:*:*:*:*:*:*" + ], + "files.map": [ + "etc/ssl/ct_log_list.cnf", + "etc/ssl/ct_log_list.cnf.dist", + "etc/ssl/openssl.cnf", + "etc/ssl/openssl.cnf.dist", + "etc/ssl/misc/CA.pl", + "etc/ssl/misc/tsget", + "etc/ssl/misc/tsget.pl", + "lib/libcrypto.so.3", + "usr/lib/libcrypto.so.3", + "usr/lib/engines-3/afalg.so", + "usr/lib/engines-3/capi.so", + "usr/lib/engines-3/loader_attic.so", + "usr/lib/engines-3/padlock.so", + "usr/lib/ossl-modules/legacy.so" + ] + }, + { + "purl": "pkg:apk/alpine/libssl3@1701101689%3A3.1.4-r2?arch=aarch64\u0026distro=alpine-3.19.0\u0026epoch=1701101689", + "version": "1701101689:3.1.4-r2", + "name": "libssl3", + "format": "apk", + "cpes.map": [ + "cpe:2.3:a:libssl3:libssl3:1701101689:aarch64:*:*:*:*:*:*" + ], + "files.map": [ + "lib/libssl.so.3", + "usr/lib/libssl.so.3" + ] + }, + { + "purl": "pkg:apk/alpine/musl@1699271358%3A1.2.4_git20230717-r4?arch=aarch64\u0026distro=alpine-3.19.0\u0026epoch=1699271358", + "version": "1699271358:1.2.4_git20230717-r4", + "name": "musl", + "format": "apk", + "cpes.map": [ + "cpe:2.3:a:musl:musl:1699271358:aarch64:*:*:*:*:*:*" + ], + "files.map": [ + "lib/ld-musl-aarch64.so.1", + "lib/libc.musl-aarch64.so.1" + ] + }, + { + "purl": "pkg:apk/alpine/musl-utils@1699271358%3A1.2.4_git20230717-r4?arch=aarch64\u0026distro=alpine-3.19.0\u0026epoch=1699271358", + "version": "1699271358:1.2.4_git20230717-r4", + "name": "musl-utils", + "format": "apk", + "cpes.map": [ + "cpe:2.3:a:musl-utils:musl-utils:1699271358:aarch64:*:*:*:*:*:*" + ], + "files.map": [ + "sbin/ldconfig", + "usr/bin/getconf", + "usr/bin/getent", + "usr/bin/iconv", + "usr/bin/ldd" + ] + }, + { + "purl": "pkg:apk/alpine/scanelf@1687178519%3A1.3.7-r2?arch=aarch64\u0026distro=alpine-3.19.0\u0026epoch=1687178519", + "version": "1687178519:1.3.7-r2", + "name": "scanelf", + "format": "apk", + "cpes.map": [ + "cpe:2.3:a:scanelf:scanelf:1687178519:aarch64:*:*:*:*:*:*" + ], + "files.map": [ + "usr/bin/scanelf" + ] + }, + { + "purl": "pkg:apk/alpine/ssl_client@1699383189%3A1.36.1-r15?arch=aarch64\u0026distro=alpine-3.19.0\u0026epoch=1699383189", + "version": "1699383189:1.36.1-r15", + "name": "ssl_client", + "format": "apk", + "cpes.map": [ + "cpe:2.3:a:ssl_client:ssl_client:1699383189:aarch64:*:*:*:*:*:*" + ], + "files.map": [ + "usr/bin/ssl_client" + ] + }, + { + "purl": "pkg:apk/alpine/zlib@1698371416%3A1.3-r2?arch=aarch64\u0026distro=alpine-3.19.0\u0026epoch=1698371416", + "version": "1698371416:1.3-r2", "name": "zlib", + "format": "apk", "cpes.map": [ - "cpe:2.3:a:zlib:zlib:1681228881:x86_64:*:*:*:*:amd64:*" + "cpe:2.3:a:zlib:zlib:1698371416:aarch64:*:*:*:*:*:*" + ], + "files.map": [ + "lib/libz.so.1", + "lib/libz.so.1.3" ] } ] diff --git a/sbom/list.go b/sbom/textlist.go similarity index 63% rename from sbom/list.go rename to sbom/textlist.go index 11de1a0fe5..e4c54e08e9 100644 --- a/sbom/list.go +++ b/sbom/textlist.go @@ -11,10 +11,30 @@ import ( "strings" ) -type CliList struct { +type cliOption func(*cLiOpts) + +type cLiOpts struct { + RenderWithEvidences bool +} + +func WithEvidences() cliOption { + return func(opts *cLiOpts) { + opts.RenderWithEvidences = true + } +} + +type TextList struct { + opts cLiOpts } -func (s *CliList) Render(w io.Writer, bom *Sbom) error { +func (s *TextList) ApplyOptions(opts ...cliOption) { + for _, opt := range opts { + opt(&s.opts) + } +} + +func (s *TextList) Render(w io.Writer, bom *Sbom) error { + sort.SliceStable(bom.Packages, func(i, j int) bool { if bom.Packages[i].Name != bom.Packages[j].Name { return bom.Packages[i].Name < bom.Packages[j].Name @@ -42,11 +62,21 @@ func (s *CliList) Render(w io.Writer, bom *Sbom) error { sb.WriteString(pkg.Architecture) } + // we only print the location if it is not empty if pkg.Location != "" { sb.WriteString(" ") sb.WriteString(termenv.String(pkg.Location).Foreground(colors.DefaultColorTheme.Disabled).String()) } + if s.opts.RenderWithEvidences { + for i := range pkg.Evidences { + evidence := pkg.Evidences[i] + sb.WriteString("\n") + sb.WriteString(termenv.String(" ").Foreground(colors.DefaultColorTheme.Disabled).String()) + sb.WriteString(termenv.String(evidence.Value).Foreground(colors.DefaultColorTheme.Disabled).String()) + } + } + sb.WriteString("\n") _, err := w.Write([]byte(sb.String()))