Skip to content

Commit

Permalink
⭐️ add evidence of package files on disk into sbom
Browse files Browse the repository at this point in the history
  • Loading branch information
chris-rock committed Feb 14, 2024
1 parent 6595398 commit b1ddc6c
Show file tree
Hide file tree
Showing 14 changed files with 631 additions and 89 deletions.
14 changes: 14 additions & 0 deletions apps/cnquery/cmd/sbom.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand All @@ -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) {},
Expand Down Expand Up @@ -100,6 +106,14 @@ 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 {
log.Info().Msg("Enabling evidences")
x.ApplyOptions(sbom.WithEvidences())
}
}

for _, bom := range boms {
output := bytes.Buffer{}
err := exporter.Render(&output, &bom)
Expand Down
4 changes: 3 additions & 1 deletion sbom/cnquery_bom_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
31 changes: 29 additions & 2 deletions sbom/cyclonedx.go
Original file line number Diff line number Diff line change
Expand Up @@ -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_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)
Expand Down
4 changes: 3 additions & 1 deletion sbom/cyclonedx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
2 changes: 1 addition & 1 deletion sbom/formats.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,6 @@ func NewExporter(fomat string) Exporter {
case FormatList:
fallthrough
default:
return &CliList{}
return &TextList{}
}
}
16 changes: 10 additions & 6 deletions sbom/report_collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down
46 changes: 36 additions & 10 deletions sbom/sbom.go
Original file line number Diff line number Diff line change
Expand Up @@ -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_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_File,
Value: pkg.FilePath,
})
}

for _, filepath := range pkg.FilePaths {
bomPkg.Evidences = append(bomPkg.Evidences, &Evidence{
Type: EvidenceType_File,
Value: filepath,
})
}

bom.Packages = append(bom.Packages, bomPkg)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion sbom/sbom.mql.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Loading

0 comments on commit b1ddc6c

Please sign in to comment.