diff --git a/examples/trivy.go b/examples/trivy.go index 24e7333..a61fef9 100644 --- a/examples/trivy.go +++ b/examples/trivy.go @@ -53,7 +53,7 @@ func main() { log.Fatal(err) } printArtifacts(artifacts) - + fmt.Println("Scanning namespace 'default', resource 'deployment/orion'") //trivy k8s --namespace default deployment/orion @@ -115,7 +115,7 @@ func main() { fmt.Println(a.RawResource) } - b, err := cluster.CreateClusterPkgBom(ctx) + b, err := cluster.CreateClusterBom(ctx) if err != nil { log.Fatal(err) } diff --git a/pkg/bom/model.go b/pkg/bom/model.go index 37f0e88..bd20d35 100644 --- a/pkg/bom/model.go +++ b/pkg/bom/model.go @@ -1,10 +1,10 @@ package bom type Result struct { - Target string `json:"Target"` - Class string `json:"Class,omitempty"` - Type string `json:"Type,omitempty"` - Packages []Package `json:"Packages,omitempty"` + ID string `json:"name"` + Type string `json:"type,omitempty"` + Coponents []Component `json:"packages,omitempty"` + NodesInfo []NodeInfo `json:"NodesInfo,omitempty"` } type Package struct { @@ -21,29 +21,27 @@ type KeyValue struct { Value string } -type TargetMetadata struct { - Name string `json:"name,omitempty"` - Version string `json:"version,omitempty"` -} - type Component struct { - Type string `json:"type,omitempty"` - Name string `json:"name,omitempty"` - Version string `json:"version,omitempty"` - Repository string `json:"repository,omitempty"` - Registry string `json:"registry,omitempty"` - Digest string `json:"digest,omitempty"` + Type string `json:"type,omitempty"` + Parents []string `json:"parents,omitempty"` + ID string `json:"id,omitempty"` + Version string `json:"version,omitempty"` + Repository string `json:"repository,omitempty"` + Registry string `json:"registry,omitempty"` + Digest string `json:"digest,omitempty"` } type NodeInfo struct { - NodeRole string `json:"node_role,omitempty"` - NodeName string `json:"node_name,omitempty"` - KubeletVersion string `json:"kubelet_version,omitempty"` - ContainerRuntimeVersion string `json:"container_runtime_version,omitempty"` - OsImage string `json:"os_image,omitempty"` - Hostname string `json:"host_name,omitempty"` - KernelVersion string `json:"kernel_version,omitempty"` - KubeProxyVersion string `json:"kube_proxy_version,omitempty"` - OperatingSystem string `json:"operating_system,omitempty"` - Architecture string `json:"architecture,omitempty"` + NodeRole string `json:"node_role,omitempty"` + NodeName string `json:"node_name,omitempty"` + Parents []string `json:"parents,omitempty"` + KubeletVersion string `json:"kubelet_version,omitempty"` + ContainerRuntimeVersion string `json:"container_runtime_version,omitempty"` + OsImage string `json:"os_image,omitempty"` + Hostname string `json:"host_name,omitempty"` + KernelVersion string `json:"kernel_version,omitempty"` + KubeProxyVersion string `json:"kube_proxy_version,omitempty"` + OperatingSystem string `json:"operating_system,omitempty"` + Architecture string `json:"architecture,omitempty"` + Images []string `json:"images,omitempty"` } diff --git a/pkg/k8s/k8s.go b/pkg/k8s/k8s.go index e09ac76..9d09ce5 100644 --- a/pkg/k8s/k8s.go +++ b/pkg/k8s/k8s.go @@ -19,6 +19,7 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" + "k8s.io/utils/strings/slices" ) const ( @@ -72,7 +73,7 @@ type Cluster interface { // a string with the resource kind GetGVR(string) (schema.GroupVersionResource, error) // CreatePkgBom returns a k8s client set - CreateClusterPkgBom(ctx context.Context) (*bom.Result, error) + CreateClusterBom(ctx context.Context) (*bom.Result, error) } type cluster struct { @@ -265,11 +266,9 @@ func getNamespaceResources() []string { } } -func (c *cluster) CreateClusterPkgBom(ctx context.Context) (*bom.Result, error) { - nodesInfo := c.CollectNodes() +func (c *cluster) CreateClusterBom(ctx context.Context) (*bom.Result, error) { // collect addons info var components []bom.Component - var err error labels := map[string]string{ k8sComponentNamespace: "component", } @@ -281,11 +280,7 @@ func (c *cluster) CreateClusterPkgBom(ctx context.Context) (*bom.Result, error) "openshift-etcd": "etcd", } } - components, err = c.collectComponents(ctx, labels) - if err != nil { - return nil, err - } - metadata, err := c.targetMetadata() + components, err := c.collectComponents(ctx, labels) if err != nil { return nil, err } @@ -296,7 +291,12 @@ func (c *cluster) CreateClusterPkgBom(ctx context.Context) (*bom.Result, error) if err != nil { return nil, err } - return c.getClusterPackages(metadata, components, addons, nodesInfo), nil + components = append(components, addons...) + nodesInfo, err := c.CollectNodes(components) + if err != nil { + return nil, err + } + return c.getClusterBomInfo(components, nodesInfo) } func (c *cluster) GetBaseComponent(imageRef name.Reference, imageName name.Reference) (bom.Component, error) { @@ -310,16 +310,21 @@ func (c *cluster) GetBaseComponent(imageRef name.Reference, imageName name.Refer return bom.Component{ Repository: repoName, Registry: registryName, - Name: fmt.Sprintf("%s:%s", repoName, imageName.Identifier()), + ID: fmt.Sprintf("%s:%s", repoName, imageName.Identifier()), Digest: imageRef.Context().Digest(imageRef.Identifier()).DigestStr(), Version: imageName.Identifier(), }, nil } -func (c *cluster) CollectNodes() []bom.NodeInfo { +func (c *cluster) CollectNodes(components []bom.Component) ([]bom.NodeInfo, error) { + name, version, err := c.ClusterNameVersion() + if err != nil { + return []bom.NodeInfo{}, err + } + parent := fmt.Sprintf("%s:%s", name, version) nodes, err := c.clientset.CoreV1().Nodes().List(context.Background(), metav1.ListOptions{}) if err != nil { - panic(err.Error()) + return []bom.NodeInfo{}, err } nodesInfo := make([]bom.NodeInfo, 0) for _, node := range nodes.Items { @@ -330,6 +335,15 @@ func (c *cluster) CollectNodes() []bom.NodeInfo { if _, ok := node.Labels["node-role.kubernetes.io/master"]; ok { nodeRole = "master" } + images := make([]string, 0) + for _, image := range node.Status.Images { + for _, c := range components { + id := fmt.Sprintf("%s/%s", c.Registry, c.ID) + if slices.Contains(image.Names, id) { + images = append(images, id) + } + } + } nodesInfo = append(nodesInfo, bom.NodeInfo{ NodeName: node.Name, KubeletVersion: node.Status.NodeInfo.KubeletVersion, @@ -341,9 +355,11 @@ func (c *cluster) CollectNodes() []bom.NodeInfo { OperatingSystem: node.Status.NodeInfo.OperatingSystem, Architecture: node.Status.NodeInfo.Architecture, NodeRole: nodeRole, + Images: images, + Parents: []string{parent}, }) } - return nodesInfo + return nodesInfo, nil } func getPodsInfo(ctx context.Context, clientset *kubernetes.Clientset, labelSelector string, namespace string) *corev1.PodList { @@ -386,108 +402,29 @@ func (c *cluster) isOpenShift() bool { return !k8sapierror.IsNotFound(err) } -func (c *cluster) getClusterPackages(metadata bom.TargetMetadata, components []bom.Component, Addons []bom.Component, nodeInfo []bom.NodeInfo) *bom.Result { - packages := make([]bom.Package, 0) - components = append(components, Addons...) - for _, c := range components { - packages = append(packages, bom.Package{ - ID: fmt.Sprintf("%s@%s", c.Name, c.Version), - Name: c.Name, - Version: c.Version, - Digest: c.Digest, - }) +func (c *cluster) getClusterBomInfo(components []bom.Component, nodeInfo []bom.NodeInfo) (*bom.Result, error) { + name, version, err := c.ClusterNameVersion() + if err != nil { + return nil, err } - nodePkgs := c.nodeInfoToPkg(nodeInfo, metadata.Version) - packages = append(packages, nodePkgs...) br := &bom.Result{ - Packages: packages, - Target: fmt.Sprintf("%s@%s", metadata.Name, metadata.Version), - Type: "Cluster", - Class: "Kubertnetes", - } - return br -} - -func (c *cluster) nodeInfoToPkg(nodesInfo []bom.NodeInfo, version string) []bom.Package { - packages := make([]bom.Package, 0) - for _, n := range nodesInfo { - kubeletPkg := bom.Package{ - ID: fmt.Sprintf("%s@%s", "kubelet", n.KubeletVersion), - Name: "kubelet", - Version: n.KubeletVersion, - } - packages = append(packages, kubeletPkg) - kubeProxyPkg := bom.Package{ - ID: fmt.Sprintf("%s@%s", "kube-proxy", n.KubeProxyVersion), - Name: "kube-proxy", - Version: n.KubeProxyVersion, - } - packages = append(packages, kubeProxyPkg) - runtimeParts := strings.Split(n.ContainerRuntimeVersion, "://") - runtimeName := strings.TrimSpace(runtimeParts[0]) - runtimeVersion := strings.TrimSpace(runtimeParts[1]) - containerdPkg := bom.Package{ - ID: fmt.Sprintf("pkg:%s@v%s", runtimeName, runtimeVersion), - Name: runtimeName, - Version: runtimeVersion, - } - packages = append(packages, containerdPkg) - osParts := strings.Split(n.OsImage, " ") - osName := strings.TrimSpace(osParts[0]) - osVersion := strings.TrimSpace(osParts[1]) - osPkg := bom.Package{ - ID: fmt.Sprintf("%s@%s", osName, osVersion), - Name: osName, - Version: osVersion, - DependsOn: []string{kubeletPkg.Name, kubeProxyPkg.Name, containerdPkg.Name}, - Properties: []bom.KeyValue{ - { - Name: "architecture", - Value: n.Architecture, - }, - { - Name: "kernel_version", - Value: n.KernelVersion, - }, - { - Name: "operating_system", - Value: n.OperatingSystem, - }, - }, - } - packages = append(packages, osPkg) - packages = append(packages, bom.Package{ - ID: fmt.Sprintf("%s@%s", n.NodeName, version), - Name: n.NodeName, - Version: version, - DependsOn: []string{osPkg.Name}, - Properties: []bom.KeyValue{ - { - Name: "node-role", - Value: n.NodeRole, - }, - { - Name: "host_name", - Value: n.Hostname, - }, - }, - }) + Coponents: components, + ID: fmt.Sprintf("%s@%s", name, version), + Type: "Cluster", + NodesInfo: nodeInfo, } - return packages + return br, nil } -func (c *cluster) targetMetadata() (bom.TargetMetadata, error) { +func (c *cluster) ClusterNameVersion() (string, string, error) { rawCfg, err := c.cConfig.RawConfig() if err != nil { - return bom.TargetMetadata{}, err + return "", "", err } clusterName := rawCfg.Contexts[rawCfg.CurrentContext].Cluster version, err := c.clientset.ServerVersion() if err != nil { - return bom.TargetMetadata{}, err + return "", "", err } - return bom.TargetMetadata{ - Name: clusterName, - Version: version.GitVersion, - }, nil + return clusterName, version.GitVersion, nil }