Skip to content

Commit 2ea217c

Browse files
authored
feat(cyclonedx): Include sbom main component info for Trivy (#1991)
Signed-off-by: Javier Rodriguez <javier@chainloop.dev>
1 parent a208356 commit 2ea217c

File tree

1 file changed

+36
-15
lines changed

1 file changed

+36
-15
lines changed

pkg/attestation/crafter/materials/cyclonedxjson.go

+36-15
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,33 @@ import (
3131
"github.com/rs/zerolog"
3232
)
3333

34-
// containerComponentKind is the kind of the main component when it's a container
35-
const containerComponentKind = "container"
34+
const (
35+
// containerComponentKind is the kind of the main component when it's a container
36+
containerComponentKind = "container"
37+
// aquaTrivyRepoDigestPropertyKey is the key used by Aqua Trivy to store the repo digest
38+
aquaTrivyRepoDigestPropertyKey = "aquasecurity:trivy:RepoDigest"
39+
)
3640

3741
type CyclonedxJSONCrafter struct {
3842
backend *casclient.CASBackend
3943
*crafterCommon
4044
}
4145

46+
// mainComponentStruct internal struct to unmarshall the incoming CycloneDX JSON
47+
type mainComponentStruct struct {
48+
Metadata struct {
49+
Component struct {
50+
Name string `json:"name"`
51+
Type string `json:"type"`
52+
Version string `json:"version"`
53+
Properties []struct {
54+
Name string `json:"name"`
55+
Value string `json:"value"`
56+
} `json:"properties"`
57+
} `json:"component"`
58+
} `json:"metadata"`
59+
}
60+
4261
func NewCyclonedxJSONCrafter(materialSchema *schemaapi.CraftingSchema_Material, backend *casclient.CASBackend, l *zerolog.Logger) (*CyclonedxJSONCrafter, error) {
4362
if materialSchema.Type != schemaapi.CraftingSchema_Material_SBOM_CYCLONEDX_JSON {
4463
return nil, fmt.Errorf("material type is not cyclonedx json")
@@ -101,24 +120,26 @@ func (i *CyclonedxJSONCrafter) Craft(ctx context.Context, filePath string) (*api
101120

102121
// extractMainComponent inspects the SBOM and extracts the main component if any and available
103122
func (i *CyclonedxJSONCrafter) extractMainComponent(rawFile []byte) (*SBOMMainComponentInfo, error) {
104-
// Define the structure of the main component in the SBOM locally to perform an unmarshal
105-
type mainComponentStruct struct {
106-
Metadata struct {
107-
Component struct {
108-
Name string `json:"name"`
109-
Type string `json:"type"`
110-
Version string `json:"version"`
111-
} `json:"component"`
112-
} `json:"metadata"`
113-
}
114-
115123
var mainComponent mainComponentStruct
116124
err := json.Unmarshal(rawFile, &mainComponent)
117125
if err != nil {
118126
return nil, fmt.Errorf("error extracting main component: %w", err)
119127
}
120128

121129
component := mainComponent.Metadata.Component
130+
131+
// If the version is empty, try to extract it from the properties
132+
if component.Version == "" {
133+
for _, prop := range component.Properties {
134+
if prop.Name == aquaTrivyRepoDigestPropertyKey {
135+
if parts := strings.Split(prop.Value, "sha256:"); len(parts) == 2 {
136+
component.Version = fmt.Sprintf("sha256:%s", parts[1])
137+
break
138+
}
139+
}
140+
}
141+
}
142+
122143
if component.Type != containerComponentKind {
123144
return &SBOMMainComponentInfo{
124145
name: component.Name,
@@ -129,13 +150,13 @@ func (i *CyclonedxJSONCrafter) extractMainComponent(rawFile []byte) (*SBOMMainCo
129150

130151
// Standardize the name to have the full repository name including the registry and
131152
// sanitize the name to remove the possible tag from the repository name
132-
stdName, err := remotename.NewRepository(strings.Split(component.Name, ":")[0])
153+
ref, err := remotename.ParseReference(component.Name)
133154
if err != nil {
134155
return nil, fmt.Errorf("couldn't parse OCI image repository name: %w", err)
135156
}
136157

137158
return &SBOMMainComponentInfo{
138-
name: stdName.String(),
159+
name: ref.Context().String(),
139160
kind: component.Type,
140161
version: component.Version,
141162
}, nil

0 commit comments

Comments
 (0)