Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

include image labels in cycloneDX SBOM #2294

Merged
merged 10 commits into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions syft/format/common/cyclonedxhelpers/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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:
Expand Down
1 change: 0 additions & 1 deletion syft/format/common/cyclonedxhelpers/decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
12 changes: 11 additions & 1 deletion syft/format/common/cyclonedxhelpers/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ func toBomDescriptor(name, version string, srcMetadata source.Description) *cycl
Version: version,
},
},
Component: toBomDescriptorComponent(srcMetadata),
Properties: toBomProperties(srcMetadata),
Component: toBomDescriptorComponent(srcMetadata),
}
}

Expand Down Expand Up @@ -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
Expand Down
94 changes: 94 additions & 0 deletions syft/format/common/cyclonedxhelpers/format_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
}
})
}
}
13 changes: 13 additions & 0 deletions syft/format/common/cyclonedxhelpers/properties.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package cyclonedxhelpers

import (
"strings"

"github.com/CycloneDX/cyclonedx-go"

"github.com/anchore/syft/syft/format/common"
Expand All @@ -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
}
2 changes: 2 additions & 0 deletions syft/source/stereoscope_image_metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions syft/source/stereoscope_image_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}

Expand Down
Loading