include image labels in cycloneDX SBOM (#2294)

* include image labels in SBOM

Signed-off-by: Benji Visser <benji@093b.org>

* update tests

Signed-off-by: Benji Visser <benji@093b.org>

* gocritic

Signed-off-by: Benji Visser <benji@093b.org>

* add properties

Signed-off-by: Benji Visser <benji@093b.org>

* add decoder

Signed-off-by: Benji Visser <benji@093b.org>

* update golden snapshots

Signed-off-by: Benji Visser <benji@093b.org>

* decodeProperties

Signed-off-by: Benji Visser <benji@093b.org>

* add test

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* remove the snapshot test changes

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* restore snapshots

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Benji Visser <benji@093b.org>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
Benji Visser 2023-11-08 15:13:04 -08:00 committed by GitHub
parent 502971a1b2
commit 0891d35e07
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 128 additions and 2 deletions

View File

@ -213,6 +213,12 @@ func extractComponents(meta *cyclonedx.Metadata) source.Description {
switch c.Type {
case cyclonedx.ComponentTypeContainer:
var labels map[string]string
if meta.Properties != nil {
labels = decodeProperties(*meta.Properties, "syft:image:labels:")
}
return source.Description{
ID: "",
// TODO: can we decode alias name-version somehow? (it isn't be encoded in the first place yet)
@ -221,6 +227,7 @@ func extractComponents(meta *cyclonedx.Metadata) source.Description {
UserInput: c.Name,
ID: c.BOMRef,
ManifestDigest: c.Version,
Labels: labels,
},
}
case cyclonedx.ComponentTypeFile:

View File

@ -97,7 +97,6 @@ func Test_decode(t *testing.T) {
CPE: "cpe:2.3:*:another:package:2:*:*:*:*:*:*:*",
PackageURL: "pkg:apk/alpine/alpine-baselayout@3.2.0-r16?arch=x86_64&upstream=alpine-baselayout&distro=alpine-3.14.2",
Properties: &[]cyclonedx.Property{
{
Name: "foundBy",
Value: "apkdb-cataloger",

View File

@ -121,6 +121,7 @@ func toBomDescriptor(name, version string, srcMetadata source.Description) *cycl
Version: version,
},
},
Properties: toBomProperties(srcMetadata),
Component: toBomDescriptorComponent(srcMetadata),
}
}
@ -190,6 +191,15 @@ func toDependencies(relationships []artifact.Relationship) []cyclonedx.Dependenc
return result
}
func toBomProperties(srcMetadata source.Description) *[]cyclonedx.Property {
metadata, ok := srcMetadata.Metadata.(source.StereoscopeImageSourceMetadata)
if ok {
props := encodeProperties(metadata.Labels, "syft:image:labels")
return &props
}
return nil
}
func toBomDescriptorComponent(srcMetadata source.Description) *cyclonedx.Component {
name := srcMetadata.Name
version := srcMetadata.Version

View File

@ -5,12 +5,14 @@ import (
"testing"
"github.com/CycloneDX/cyclonedx-go"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source"
)
func Test_formatCPE(t *testing.T) {
@ -138,3 +140,95 @@ func Test_relationships(t *testing.T) {
})
}
}
func Test_toBomDescriptor(t *testing.T) {
type args struct {
name string
version string
srcMetadata source.Description
}
tests := []struct {
name string
args args
want *cyclonedx.Metadata
}{
{
name: "with image labels source metadata",
args: args{
name: "test-image",
version: "1.0.0",
srcMetadata: source.Description{
Metadata: source.StereoscopeImageSourceMetadata{
Labels: map[string]string{
"key1": "value1",
},
},
},
},
want: &cyclonedx.Metadata{
Timestamp: "",
Lifecycles: nil,
Tools: &[]cyclonedx.Tool{
{
Vendor: "anchore",
Name: "test-image",
Version: "1.0.0",
Hashes: nil,
ExternalReferences: nil,
},
},
Authors: nil,
Component: &cyclonedx.Component{
BOMRef: "",
MIMEType: "",
Type: "container",
Supplier: nil,
Author: "",
Publisher: "",
Group: "",
Name: "",
Version: "",
Description: "",
Scope: "",
Hashes: nil,
Licenses: nil,
Copyright: "",
CPE: "",
PackageURL: "",
SWID: nil,
Modified: nil,
Pedigree: nil,
ExternalReferences: nil,
Properties: nil,
Components: nil,
Evidence: nil,
ReleaseNotes: nil,
},
Manufacture: nil,
Supplier: nil,
Licenses: nil,
Properties: &[]cyclonedx.Property{
{
Name: "syft:image:labels:key1",
Value: "value1",
},
}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
subject := toBomDescriptor(tt.args.name, tt.args.version, tt.args.srcMetadata)
require.NotEmpty(t, subject.Component.BOMRef)
subject.Timestamp = "" // not under test
require.NotNil(t, subject.Component)
require.NotEmpty(t, subject.Component.BOMRef)
subject.Component.BOMRef = "" // not under test
if d := cmp.Diff(tt.want, subject); d != "" {
t.Errorf("toBomDescriptor() mismatch (-want +got):\n%s", d)
}
})
}
}

View File

@ -1,6 +1,8 @@
package cyclonedxhelpers
import (
"strings"
"github.com/CycloneDX/cyclonedx-go"
"github.com/anchore/syft/syft/format/common"
@ -19,3 +21,14 @@ func encodeProperties(obj interface{}, prefix string) (out []cyclonedx.Property)
}
return
}
func decodeProperties(properties []cyclonedx.Property, prefix string) map[string]string {
labels := make(map[string]string)
for _, property := range properties {
if strings.HasPrefix(property.Name, prefix) {
labelName := strings.TrimPrefix(property.Name, prefix)
labels[labelName] = property.Value
}
}
return labels
}

View File

@ -18,6 +18,7 @@ type StereoscopeImageSourceMetadata struct {
Architecture string `json:"architecture"`
Variant string `json:"architectureVariant,omitempty"`
OS string `json:"os"`
Labels map[string]string `json:"labels,omitempty"`
}
// StereoscopeLayerMetadata represents all static metadata that defines what a container image layer is.
@ -48,6 +49,7 @@ func NewStereoscopeImageMetadata(img *image.Image, userInput string) Stereoscope
Architecture: img.Metadata.Architecture,
Variant: img.Metadata.Variant,
OS: img.Metadata.OS,
Labels: img.Metadata.Config.Config.Labels,
}
// populate image metadata

View File

@ -192,6 +192,7 @@ func imageMetadataFromStereoscopeImage(img *image.Image, reference string) Stere
Architecture: img.Metadata.Architecture,
Variant: img.Metadata.Variant,
OS: img.Metadata.OS,
Labels: img.Metadata.Config.Config.Labels,
}
}