Skip to content

Commit

Permalink
feat: cluster bom (#149)
Browse files Browse the repository at this point in the history
* feat: cluster bom

Signed-off-by: chenk <hen.keinan@gmail.com>

* feat: cluster bom

Signed-off-by: chenk <hen.keinan@gmail.com>

* feat: cluster bom

Signed-off-by: chenk <hen.keinan@gmail.com>

---------

Signed-off-by: chenk <hen.keinan@gmail.com>
  • Loading branch information
chen-keinan authored May 7, 2023
1 parent 019a895 commit bdeb4cd
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 133 deletions.
4 changes: 2 additions & 2 deletions examples/trivy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down
48 changes: 23 additions & 25 deletions pkg/bom/model.go
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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"`
}
149 changes: 43 additions & 106 deletions pkg/k8s/k8s.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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",
}
Expand All @@ -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
}
Expand All @@ -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) {
Expand All @@ -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 {
Expand All @@ -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,
Expand All @@ -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 {
Expand Down Expand Up @@ -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
}

0 comments on commit bdeb4cd

Please sign in to comment.