Skip to content

Commit

Permalink
refactor: collect bom info (#247)
Browse files Browse the repository at this point in the history
* refactor: collect bom info

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

* refactor: component and node collector

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

---------

Signed-off-by: chenk <hen.keinan@gmail.com>
  • Loading branch information
chen-keinan authored Nov 18, 2023
1 parent 921512b commit 26031f2
Show file tree
Hide file tree
Showing 3 changed files with 200 additions and 68 deletions.
144 changes: 79 additions & 65 deletions pkg/k8s/k8s.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
containerimage "github.com/google/go-containerregistry/pkg/name"
ms "github.com/mitchellh/mapstructure"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
k8sapierror "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -348,7 +349,7 @@ func (c *cluster) CreateClusterBom(ctx context.Context) (*bom.Result, error) {
return c.getClusterBomInfo(components, nodesInfo)
}

func (c *cluster) GetContainer(imageRef containerimage.Reference, imageName containerimage.Reference) (bom.Container, error) {
func GetContainer(imageRef containerimage.Reference, imageName containerimage.Reference) (bom.Container, error) {
repoName := imageName.Context().RepositoryStr()
registryName := imageName.Context().RegistryStr()

Expand All @@ -368,13 +369,7 @@ func (c *cluster) CollectNodes(components []bom.Component) ([]bom.NodeInfo, erro
}
nodesInfo := make([]bom.NodeInfo, 0)
for _, node := range nodes.Items {
nodeRole := "worker"
if _, ok := node.Labels["node-role.kubernetes.io/control-plane"]; ok {
nodeRole = "master"
}
if _, ok := node.Labels["node-role.kubernetes.io/master"]; ok {
nodeRole = "master"
}
nf := NodeInfo(node)
images := make([]string, 0)
for _, image := range node.Status.Images {
for _, c := range components {
Expand All @@ -386,25 +381,36 @@ func (c *cluster) CollectNodes(components []bom.Component) ([]bom.NodeInfo, erro
}
}
}
nodesInfo = append(nodesInfo, bom.NodeInfo{
NodeName: node.Name,
KubeletVersion: node.Status.NodeInfo.KubeletVersion,
ContainerRuntimeVersion: node.Status.NodeInfo.ContainerRuntimeVersion,
OsImage: node.Status.NodeInfo.OSImage,
KubeProxyVersion: node.Status.NodeInfo.KernelVersion,
Properties: map[string]string{
"NodeRole": nodeRole,
"HostName": node.ObjectMeta.Name,
"KernelVersion": node.Status.NodeInfo.KernelVersion,
"OperatingSystem": node.Status.NodeInfo.OperatingSystem,
"Architecture": node.Status.NodeInfo.Architecture,
},
Images: images,
})
nf.Images = images
nodesInfo = append(nodesInfo, nf)
}
return nodesInfo, nil
}

func NodeInfo(node v1.Node) bom.NodeInfo {
nodeRole := "worker"
if _, ok := node.Labels["node-role.kubernetes.io/control-plane"]; ok {
nodeRole = "master"
}
if _, ok := node.Labels["node-role.kubernetes.io/master"]; ok {
nodeRole = "master"
}
return bom.NodeInfo{
NodeName: node.Name,
KubeletVersion: node.Status.NodeInfo.KubeletVersion,
ContainerRuntimeVersion: node.Status.NodeInfo.ContainerRuntimeVersion,
OsImage: node.Status.NodeInfo.OSImage,
KubeProxyVersion: node.Status.NodeInfo.KubeProxyVersion,
Properties: map[string]string{
"NodeRole": nodeRole,
"HostName": node.ObjectMeta.Name,
"KernelVersion": node.Status.NodeInfo.KernelVersion,
"OperatingSystem": node.Status.NodeInfo.OperatingSystem,
"Architecture": node.Status.NodeInfo.Architecture,
},
}
}

func getPodsInfo(ctx context.Context, clientset *kubernetes.Clientset, labelSelector string, namespace string) (*corev1.PodList, error) {
pods, err := clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{LabelSelector: labelSelector})
if err != nil {
Expand All @@ -421,54 +427,62 @@ func (c *cluster) collectComponents(ctx context.Context, labels map[string]strin
continue
}
for _, pod := range pods.Items {
containers := make([]bom.Container, 0)
for _, s := range pod.Status.ContainerStatuses {
imageName, err := utils.ParseReference(s.Image)
if err != nil {
return nil, err
}
imageID := getImageID(s.ImageID, s.Image)
if len(imageID) == 0 {
continue
}
imageRef, err := utils.ParseReference(imageID)
if err != nil {
return nil, err
}
c, err := c.GetContainer(imageRef, imageName)
if err != nil {
continue
}
containers = append(containers, c)
}
props := make(map[string]string)
componentValue, ok := pod.GetLabels()[labelSelector]
if ok {
props["Name"] = pod.Name
}

repoName := upstreamRepoByName(componentValue)
if val, ok := CoreComponentPropertyType[repoName]; ok {
props["Type"] = val
}
orgName := upstreamOrgByName(repoName)
upstreamComponentName := repoName
if len(orgName) > 0 {
upstreamComponentName = fmt.Sprintf("%s/%s", orgName, repoName)
pi, err := PodInfo(pod, labelSelector)
if err != nil {
continue
}
version := trimString(findComponentVersion(containers, componentValue), []string{"v", "V"})
components = append(components, bom.Component{
Namespace: pod.Namespace,
Name: upstreamComponentName,
Version: version,
Properties: props,
Containers: containers,
})
components = append(components, *pi)
}
}
return components, nil
}

func PodInfo(pod corev1.Pod, labelSelector string) (*bom.Component, error) {
containers := make([]bom.Container, 0)
for _, s := range pod.Status.ContainerStatuses {
imageName, err := utils.ParseReference(s.Image)
if err != nil {
return nil, err
}
imageID := getImageID(s.ImageID, s.Image)
if len(imageID) == 0 {
continue
}
imageRef, err := utils.ParseReference(imageID)
if err != nil {
return nil, err
}
co, err := GetContainer(imageRef, imageName)
if err != nil {
continue
}
containers = append(containers, co)
}
props := make(map[string]string)
componentValue, ok := pod.GetLabels()[labelSelector]
if ok {
props["Name"] = pod.Name
}

repoName := upstreamRepoByName(componentValue)
if val, ok := CoreComponentPropertyType[repoName]; ok {
props["Type"] = val
}
orgName := upstreamOrgByName(repoName)
upstreamComponentName := repoName
if len(orgName) > 0 {
upstreamComponentName = fmt.Sprintf("%s/%s", orgName, repoName)
}
version := trimString(findComponentVersion(containers, componentValue), []string{"v", "V"})
return &bom.Component{
Namespace: pod.Namespace,
Name: upstreamComponentName,
Version: version,
Properties: props,
Containers: containers,
}, nil
}

func findComponentVersion(containers []bom.Container, name string) string {
for _, c := range containers {
if strings.Contains(c.ID, name) {
Expand Down
111 changes: 111 additions & 0 deletions pkg/k8s/k8s_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ package k8s
import (
"testing"

"github.com/aquasecurity/trivy-kubernetes/pkg/bom"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
Expand Down Expand Up @@ -108,3 +112,110 @@ func createValidTestConfig(namespace string) clientcmd.ClientConfig {

return clientcmd.NewNonInteractiveClientConfig(*config, "cluster1", &clientcmd.ConfigOverrides{}, nil)
}

func TestPodInfo(t *testing.T) {
tests := []struct {
Name string
pod corev1.Pod
labelSelector string
want *bom.Component
}{
{
Name: "pod with label",
labelSelector: "component",
pod: corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod1",
Namespace: "kube-system",
Labels: map[string]string{"component": "kube-apiserver"},
},
Status: corev1.PodStatus{
ContainerStatuses: []corev1.ContainerStatus{{
Image: "k8s.gcr.io/kube-apiserver:v1.21.1",
ImageID: "sha256:18e61c783b41758dd391ab901366ec3546b26fae00eef7e223d1f94da808e02f",
},
},
},
},
want: &bom.Component{
Namespace: "kube-system",
Name: "k8s.io/apiserver",
Version: "1.21.1",
Properties: map[string]string{
"Name": "pod1",
"Type": "controlPlane",
},
Containers: []bom.Container{
{
ID: "kube-apiserver:v1.21.1",
Version: "v1.21.1",
Repository: "kube-apiserver",
Registry: "k8s.gcr.io",
Digest: "18e61c783b41758dd391ab901366ec3546b26fae00eef7e223d1f94da808e02f",
},
},
},
},
}
for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
got, err := PodInfo(test.pod, test.labelSelector)
assert.NoError(t, err)
assert.Equal(t, got, test.want)
})
}
}

func TestNodeInfo(t *testing.T) {
tests := []struct {
Name string
node v1.Node
labelSelector string
want bom.NodeInfo
}{
{
Name: "node info ",
node: v1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node1",
Labels: map[string]string{
"component": "kube-apiserver",
"node-role.kubernetes.io/master": "",
},
},
Status: v1.NodeStatus{
NodeInfo: v1.NodeSystemInfo{
Architecture: "amd64",
ContainerRuntimeVersion: "containerd://1.5.2",
KubeletVersion: "v1.21.1",
KernelVersion: "6.5.9-300.fc39.aarch64",
OperatingSystem: "linux",
OSImage: "Ubuntu 21.04",
KubeProxyVersion: "v1.21.1",
},
},
},

want: bom.NodeInfo{
NodeName: "node1",
KubeletVersion: "v1.21.1",
KubeProxyVersion: "v1.21.1",
ContainerRuntimeVersion: "containerd://1.5.2",
OsImage: "Ubuntu 21.04",
Properties: map[string]string{
"NodeRole": "master",
"HostName": "node1",
"KernelVersion": "6.5.9-300.fc39.aarch64",
"OperatingSystem": "linux",
"Architecture": "amd64",
},
},
},
}
for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
got := NodeInfo(test.node)
assert.Equal(t, got, test.want)
})
}
}
13 changes: 10 additions & 3 deletions pkg/trivyk8s/trivyk8s.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,12 +217,17 @@ func (c *client) ListArtifactAndNodeInfo(ctx context.Context, namespace string,

// ListBomInfo returns kubernetes Bom (node,core components and etc) information.
func (c *client) ListBomInfo(ctx context.Context) ([]*artifacts.Artifact, error) {
artifactList := make([]*artifacts.Artifact, 0)

b, err := c.cluster.CreateClusterBom(ctx)
if err != nil {
return []*artifacts.Artifact{}, err
}
return BomToArtifacts(b)

}

func BomToArtifacts(b *bom.Result) ([]*artifacts.Artifact, error) {
artifactList := make([]*artifacts.Artifact, 0)
for _, c := range b.Components {
rawResource, err := rawResource(&c)
if err != nil {
Expand All @@ -238,9 +243,11 @@ func (c *client) ListBomInfo(ctx context.Context) ([]*artifacts.Artifact, error)
artifactList = append(artifactList, &artifacts.Artifact{Kind: "NodeComponents", Name: ni.NodeName, RawResource: rawResource})
}
cr, err := rawResource(&bom.Result{ID: b.ID, Type: "ClusterInfo", Version: b.Version, Properties: b.Properties})
if err != nil {
return []*artifacts.Artifact{}, err
}
artifactList = append(artifactList, &artifacts.Artifact{Kind: "Cluster", Name: b.ID, RawResource: cr})
return artifactList, err

return artifactList, nil
}

func rawResource(resource interface{}) (map[string]interface{}, error) {
Expand Down

0 comments on commit 26031f2

Please sign in to comment.