mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 16:33:21 +01:00
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:
parent
502971a1b2
commit
0891d35e07
@ -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:
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user