Skip to content

Commit 37e4fa0

Browse files
Add better support for cyclonedx
Signed-off-by: Mateusz Dymiński <[email protected]>
1 parent 2abbc88 commit 37e4fa0

File tree

7 files changed

+179
-78
lines changed

7 files changed

+179
-78
lines changed

cmd/cyclonexdx.go

Lines changed: 41 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package cmd
33
import (
44
"fmt"
55
"hash/fnv"
6+
"slices"
67
"time"
78

89
"github.com/CycloneDX/cyclonedx-go"
@@ -12,8 +13,10 @@ import (
1213
)
1314

1415
const (
15-
CdxPrefix = "cdx:"
16-
KSOCPrefix = "ksoc:kbom:"
16+
CdxPrefix = "cdx:"
17+
KSOCPrefix = "ksoc:kbom:"
18+
K8sComponentType = "k8s:component:type"
19+
K8sComponentName = "k8s:component:name"
1720

1821
ClusterType = "cluster"
1922
NodeType = "node"
@@ -33,24 +36,13 @@ func transformToCycloneDXBOM(kbom *model.KBOM) *cyclonedx.BOM { //nolint:funlen
3336
Version: kbom.GeneratedBy.Version,
3437
},
3538
},
36-
Component: &cyclonedx.Component{
37-
BOMRef: id(kbom.GeneratedBy),
38-
Type: cyclonedx.ComponentTypeApplication,
39-
Name: kbom.GeneratedBy.Name,
40-
Hashes: &[]cyclonedx.Hash{
41-
{
42-
Algorithm: cyclonedx.HashAlgoSHA256,
43-
Value: kbom.GeneratedBy.Commit,
44-
},
45-
},
46-
Version: kbom.GeneratedBy.Version,
47-
},
4839
}
4940

5041
components := []cyclonedx.Component{}
42+
dependencies := []cyclonedx.Dependency{}
5143
clusterProperties := []cyclonedx.Property{
5244
{
53-
Name: CdxPrefix + "k8s:component:type",
45+
Name: CdxPrefix + K8sComponentType,
5446
Value: ClusterType,
5547
},
5648
{
@@ -85,28 +77,29 @@ func transformToCycloneDXBOM(kbom *model.KBOM) *cyclonedx.BOM { //nolint:funlen
8577
}
8678

8779
clusterComponent := cyclonedx.Component{
88-
BOMRef: id(kbom.Cluster),
80+
BOMRef: kbom.Cluster.BOMRef(),
8981
Type: cyclonedx.ComponentTypePlatform,
90-
Name: "cluster",
82+
Name: kbom.Cluster.BOMName(),
9183
Version: kbom.Cluster.K8sVersion,
9284
Properties: &clusterProperties,
9385
}
86+
cdxBOM.Metadata.Component = &clusterComponent
9487

95-
components = append(components, clusterComponent)
96-
88+
clusterDependencies := make(map[string]string)
9789
for i := range kbom.Cluster.Nodes {
9890
n := kbom.Cluster.Nodes[i]
91+
bomRef := id(n)
9992
components = append(components, cyclonedx.Component{
100-
BOMRef: id(n),
93+
BOMRef: bomRef,
10194
Type: cyclonedx.ComponentTypePlatform,
10295
Name: n.Name,
10396
Properties: &[]cyclonedx.Property{
10497
{
105-
Name: CdxPrefix + "k8s:component:type",
98+
Name: CdxPrefix + K8sComponentType,
10699
Value: NodeType,
107100
},
108101
{
109-
Name: CdxPrefix + "k8s:component:name",
102+
Name: CdxPrefix + K8sComponentName,
110103
Value: n.Name,
111104
},
112105
{
@@ -187,22 +180,24 @@ func transformToCycloneDXBOM(kbom *model.KBOM) *cyclonedx.BOM { //nolint:funlen
187180
},
188181
},
189182
})
183+
clusterDependencies[bomRef] = bomRef
190184
}
191185

192186
for _, img := range kbom.Cluster.Components.Images {
187+
bomRef := img.PkgID()
193188
container := cyclonedx.Component{
194-
BOMRef: img.PkgID(),
189+
BOMRef: bomRef,
195190
Type: cyclonedx.ComponentTypeContainer,
196191
Name: img.Name,
197192
Version: img.Digest,
198-
PackageURL: img.PkgID(),
193+
PackageURL: bomRef,
199194
Properties: &[]cyclonedx.Property{
200195
{
201-
Name: CdxPrefix + "k8s:component:type",
196+
Name: CdxPrefix + K8sComponentType,
202197
Value: ContainerType,
203198
},
204199
{
205-
Name: CdxPrefix + "k8s:component:name",
200+
Name: CdxPrefix + K8sComponentName,
206201
Value: img.Name,
207202
},
208203
{
@@ -225,17 +220,21 @@ func transformToCycloneDXBOM(kbom *model.KBOM) *cyclonedx.BOM { //nolint:funlen
225220
}
226221

227222
components = append(components, container)
223+
224+
if img.ControlPlane {
225+
clusterDependencies[bomRef] = bomRef
226+
}
228227
}
229228

230229
for _, resList := range kbom.Cluster.Components.Resources {
231230
for _, res := range resList.Resources {
232231
properties := []cyclonedx.Property{
233232
{
234-
Name: CdxPrefix + "k8s:component:type",
233+
Name: CdxPrefix + K8sComponentType,
235234
Value: resList.Kind,
236235
},
237236
{
238-
Name: CdxPrefix + "k8s:component:name",
237+
Name: CdxPrefix + K8sComponentName,
239238
Value: res.Name,
240239
},
241240
{
@@ -263,9 +262,21 @@ func transformToCycloneDXBOM(kbom *model.KBOM) *cyclonedx.BOM { //nolint:funlen
263262
}
264263
}
265264

266-
cdxBOM.Components = &components
265+
clusterDependenciesArr := make([]string, 0)
266+
for _, dep := range clusterDependencies {
267+
clusterDependenciesArr = append(clusterDependenciesArr, dep)
268+
}
269+
slices.Sort(clusterDependenciesArr)
267270

268-
// TODO: add relationships and dependencies
271+
dependencies = append(dependencies,
272+
cyclonedx.Dependency{
273+
Ref: clusterComponent.BOMRef,
274+
Dependencies: &clusterDependenciesArr,
275+
},
276+
)
277+
278+
cdxBOM.Components = &components
279+
cdxBOM.Dependencies = &dependencies
269280

270281
return cdxBOM
271282
}

docs/schema.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
## KBOM Schema
1+
# KBOM Schema
22

33
The section below describes the high level object model for KBOM.
44

5-
Cluster Details
5+
## Cluster Details
6+
7+
Instances:
68

7-
Instances
89
- Name
910
- Hostname
1011
- CloudType
@@ -18,13 +19,15 @@ Instances
1819
- Kubelet Version
1920
- Kube Proxy Version
2021

21-
Images
22+
Images:
23+
2224
- Name
2325
- FullName
2426
- Version
2527
- Digest
2628

27-
KubeObjects
29+
KubeObjects:
30+
2831
- Kind
2932
- Api Version
3033
- Count

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.20
55
require (
66
github.com/CycloneDX/cyclonedx-go v0.7.2
77
github.com/Masterminds/semver v1.5.0
8+
github.com/distribution/reference v0.5.0
89
github.com/google/uuid v1.4.0
910
github.com/invopop/jsonschema v0.12.0
1011
github.com/mitchellh/hashstructure/v2 v2.0.2
@@ -48,6 +49,7 @@ require (
4849
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
4950
github.com/modern-go/reflect2 v1.0.2 // indirect
5051
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
52+
github.com/opencontainers/go-digest v1.0.0 // indirect
5153
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
5254
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
5355
github.com/sagikazarmark/locafero v0.4.0 // indirect

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
1414
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
1515
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
1616
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
17+
github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
18+
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
1719
github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE=
1820
github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
1921
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
@@ -89,6 +91,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
8991
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
9092
github.com/onsi/ginkgo/v2 v2.9.4 h1:xR7vG4IXt5RWx6FfIjyAtsoMAtnc3C/rFXBBd2AjZwE=
9193
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
94+
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
95+
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
9296
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
9397
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
9498
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=

internal/kube/kube.go

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"strings"
99

1010
"github.com/Masterminds/semver"
11+
"github.com/distribution/reference"
1112
"github.com/rs/zerolog/log"
1213
v1 "k8s.io/api/core/v1"
1314
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -172,38 +173,40 @@ func (k *k8sDB) AllImages(ctx context.Context) ([]model.Image, error) {
172173
if err != nil {
173174
return nil, fmt.Errorf("failed to list pods: %w", err)
174175
}
176+
namespace := namespaces.Items[i].Name
175177

176-
log.Debug().Str("namespace", namespaces.Items[i].Name).Int("count", len(pods.Items)).Msg("Found pods in namespace")
178+
log.Debug().Str("namespace", namespace).Int("count", len(pods.Items)).Msg("Found pods in namespace")
177179

178180
for j := range pods.Items {
179181
pod := pods.Items[j]
180182

181183
for k := range pod.Spec.InitContainers {
182-
img, err := containerToImage(pod.Spec.InitContainers[k].Image, pod.Spec.InitContainers[k].Name, pod.Status.InitContainerStatuses)
184+
img, err := containerToImage(pod.Spec.InitContainers[k].Image,
185+
pod.Spec.InitContainers[k].Name, pod.Status.InitContainerStatuses, namespace)
183186
if err != nil {
184187
return nil, err
185188
}
186189

187-
images[img.Name] = *img
190+
images[img.FullName] = *img
188191
}
189192

190193
for k := range pod.Spec.Containers {
191-
img, err := containerToImage(pod.Spec.Containers[k].Image, pod.Spec.Containers[k].Name, pod.Status.ContainerStatuses)
194+
img, err := containerToImage(pod.Spec.Containers[k].Image, pod.Spec.Containers[k].Name, pod.Status.ContainerStatuses, namespace)
192195
if err != nil {
193196
return nil, err
194197
}
195198

196-
images[img.Name] = *img
199+
images[img.FullName] = *img
197200
}
198201

199202
for k := range pod.Spec.EphemeralContainers {
200203
img, err := containerToImage(pod.Spec.EphemeralContainers[k].Image,
201-
pod.Spec.EphemeralContainers[k].Name, pod.Status.EphemeralContainerStatuses)
204+
pod.Spec.EphemeralContainers[k].Name, pod.Status.EphemeralContainerStatuses, namespace)
202205
if err != nil {
203206
return nil, err
204207
}
205208

206-
images[img.Name] = *img
209+
images[img.FullName] = *img
207210
}
208211
}
209212
}
@@ -216,20 +219,35 @@ func (k *k8sDB) AllImages(ctx context.Context) ([]model.Image, error) {
216219
return toReturn, nil
217220
}
218221

219-
func containerToImage(img, imgName string, statuses []v1.ContainerStatus) (*model.Image, error) {
222+
func containerToImage(img, imgName string, statuses []v1.ContainerStatus, namespace string) (*model.Image, error) {
220223
if img == "" {
221224
return nil, fmt.Errorf("container %s has no image", img)
222225
}
223226

227+
named, err := reference.ParseNormalizedNamed(img)
228+
if err != nil {
229+
return nil, err
230+
}
231+
232+
controlPlane := false
233+
if namespace == "kube-system" {
234+
controlPlane = true
235+
}
236+
224237
res := &model.Image{
225-
FullName: img,
226-
Name: strings.Split(strings.Split(img, "@sha256:")[0], ":")[0],
227-
Version: versionFromImage(img),
238+
FullName: img,
239+
ControlPlane: controlPlane,
228240
}
229241

230-
if strings.Contains(img, "@") {
231-
res.Digest = strings.Split(img, "@")[1]
232-
return res, nil
242+
res.Name = named.Name()
243+
tagged, ok := named.(reference.Tagged)
244+
if ok {
245+
res.Version = tagged.Tag()
246+
}
247+
248+
digested, ok := named.(reference.Digested)
249+
if ok {
250+
res.Digest = digested.Digest().String()
233251
}
234252

235253
// search in statuses for ImageID to get digest
@@ -250,24 +268,6 @@ func containerToImage(img, imgName string, statuses []v1.ContainerStatus) (*mode
250268
return res, nil
251269
}
252270

253-
func versionFromImage(img string) string {
254-
if strings.Contains(img, ":") {
255-
if strings.Contains(img, "@") {
256-
withoutDigest := strings.Split(img, "@")[0]
257-
258-
if strings.Contains(withoutDigest, ":") {
259-
return strings.Split(withoutDigest, ":")[1]
260-
}
261-
262-
return ""
263-
}
264-
265-
return strings.Split(img, ":")[1]
266-
}
267-
268-
return ""
269-
}
270-
271271
// Metadata returns the kubernetes version
272272
func (k *k8sDB) Metadata(ctx context.Context) (k8sVersion, caDigest string, err error) {
273273
if _, err := rest.InClusterConfig(); err != nil {

0 commit comments

Comments
 (0)