Repository: aquasecurity/starboard
Pull request #370 (2021-01-29 12:29:30)

fix: Save security reports for static pods
Note that kublet can manage pods independently by reading pod definition
files from a configured host directory (typically /etc/kubernetes/manifests).
Such pods are called *static pods* and are owned by a cluster Node.
So far we could not save vulnerability and config audit reports for such pods.

In this commit we treat them as unmanaged pods. (Otherwise we'd require
cluster-scoped permissions to get Nodes in order to set the owner reference
when we create an instance of custom resource.)

Resolves: #234

Signed-off-by: Daniel Pacak <pacak.daniel@gmail.com>

codecov[bot]: # [Codecov](https://codecov.io/gh/aquasecurity/starboard/pull/370?src=pr&el=h1) Report
> Merging [#370](https://codecov.io/gh/aquasecurity/starboard/pull/370?src=pr&el=desc) (f6ef2e9) into [main](https://codecov.io/gh/aquasecurity/starboard/commit/ffae344d40ad450c007353a2294d9e244d750157?el=desc) (ffae344) will **decrease** coverage by `36.95%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/aquasecurity/starboard/pull/370/graphs/tree.svg?width=650&height=150&src=pr&token=qqTGKAJ0Wb)](https://codecov.io/gh/aquasecurity/starboard/pull/370?src=pr&el=tree)

```diff
@@             Coverage Diff             @@
##             main     #370       +/-   ##
===========================================
- Coverage   67.50%   30.55%   -36.96%     
===========================================
  Files          58       57        -1     
  Lines        3059     2975       -84     
===========================================
- Hits         2065      909     -1156     
- Misses        741     2003     +1262     
+ Partials      253       63      -190     
```


| [Impacted Files](https://codecov.io/gh/aquasecurity/starboard/pull/370?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pkg/resources/resources.go](https://codecov.io/gh/aquasecurity/starboard/pull/370/diff?src=pr&el=tree#diff-cGtnL3Jlc291cmNlcy9yZXNvdXJjZXMuZ28=) | `67.56% <100.00%> (+36.31%)` | :arrow_up: |
| [pkg/cmd/get.go](https://codecov.io/gh/aquasecurity/starboard/pull/370/diff?src=pr&el=tree#diff-cGtnL2NtZC9nZXQuZ28=) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pkg/cmd/find.go](https://codecov.io/gh/aquasecurity/starboard/pull/370/diff?src=pr&el=tree#diff-cGtnL2NtZC9maW5kLmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pkg/cmd/root.go](https://codecov.io/gh/aquasecurity/starboard/pull/370/diff?src=pr&el=tree#diff-cGtnL2NtZC9yb290Lmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pkg/cmd/scan.go](https://codecov.io/gh/aquasecurity/starboard/pull/370/diff?src=pr&el=tree#diff-cGtnL2NtZC9zY2FuLmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pkg/cmd/polaris.go](https://codecov.io/gh/aquasecurity/starboard/pull/370/diff?src=pr&el=tree#diff-cGtnL2NtZC9wb2xhcmlzLmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pkg/cmd/version.go](https://codecov.io/gh/aquasecurity/starboard/pull/370/diff?src=pr&el=tree#diff-cGtnL2NtZC92ZXJzaW9uLmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pkg/cmd/kube\_bench.go](https://codecov.io/gh/aquasecurity/starboard/pull/370/diff?src=pr&el=tree#diff-cGtnL2NtZC9rdWJlX2JlbmNoLmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pkg/cmd/kube\_hunter.go](https://codecov.io/gh/aquasecurity/starboard/pull/370/diff?src=pr&el=tree#diff-cGtnL2NtZC9rdWJlX2h1bnRlci5nbw==) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pkg/cmd/find\_vulnerabilities.go](https://codecov.io/gh/aquasecurity/starboard/pull/370/diff?src=pr&el=tree#diff-cGtnL2NtZC9maW5kX3Z1bG5lcmFiaWxpdGllcy5nbw==) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| ... and [29 more](https://codecov.io/gh/aquasecurity/starboard/pull/370/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/aquasecurity/starboard/pull/370?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/aquasecurity/starboard/pull/370?src=pr&el=footer). Last update [336f1ce...f6ef2e9](https://codecov.io/gh/aquasecurity/starboard/pull/370?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/Makefile b/Makefile
index 609ac6b..be2b128 100644
--- a/Makefile
+++ b/Makefile
@@ -66,13 +66,15 @@ itests-starboard: check-env get-ginkgo
 	--v \
 	-coverprofile=coverage.txt \
 	-coverpkg=github.com/aquasecurity/starboard/pkg/cmd,\
+	github.com/aquasecurity/starboard/pkg/config,\
+	github.com/aquasecurity/starboard/pkg/resources,\
 	github.com/aquasecurity/starboard/pkg/kube,\
 	github.com/aquasecurity/starboard/pkg/kube/pod,\
 	github.com/aquasecurity/starboard/pkg/kubebench,\
 	github.com/aquasecurity/starboard/pkg/kubehunter,\
+	github.com/aquasecurity/starboard/pkg/trivy,\
 	github.com/aquasecurity/starboard/pkg/polaris,\
 	github.com/aquasecurity/starboard/pkg/configauditreport,\
-	github.com/aquasecurity/starboard/pkg/trivy,\
 	github.com/aquasecurity/starboard/pkg/vulnerabilityreport \
 	./itest/starboard
 
@@ -88,7 +90,11 @@ itests-starboard-operator: check-env get-ginkgo
 	github.com/aquasecurity/starboard/pkg/operator/controller,\
 	github.com/aquasecurity/starboard/pkg/operator/controller/job,\
 	github.com/aquasecurity/starboard/pkg/operator/controller/pod,\
+	github.com/aquasecurity/starboard/pkg/config,\
+	github.com/aquasecurity/starboard/pkg/resources,\
 	github.com/aquasecurity/starboard/pkg/trivy,\
+	github.com/aquasecurity/starboard/pkg/polaris,\
+	github.com/aquasecurity/starboard/pkg/configauditreport,\
 	github.com/aquasecurity/starboard/pkg/vulnerabilityreport \
 	./itest/starboard-operator
 
diff --git a/pkg/resources/doc.go b/pkg/resources/doc.go
new file mode 100644
index 0000000..a9009e6
--- /dev/null
+++ b/pkg/resources/doc.go
@@ -0,0 +1,3 @@
+// This package provides primitives for working with Kubernetes objects.
+// TODO We should merge this package with the kube package.
+package resources
diff --git a/pkg/resources/resources.go b/pkg/resources/resources.go
index 0d4463f..6c7cd19 100644
--- a/pkg/resources/resources.go
+++ b/pkg/resources/resources.go
@@ -36,12 +36,32 @@ func GetContainerImagesFromJob(job *batchv1.Job) (kube.ContainerImages, error) {
 	return containerImages, nil
 }
 
-// GetImmediateOwnerReference returns the immediate owner of the specified Pod.
-// For example, for a Pod controlled by a Deployment it will return the active ReplicaSet object,
-// whereas for an unmanaged Pod the immediate owner is the Pod itself.
+// GetImmediateOwnerReference returns the immediate owner of the specified pod.
+// For example, for a pod controlled by a Deployment it will return the active
+// ReplicaSet, whereas for an unmanaged pod the immediate owner is the pod
+// itself.
+//
+// Note that kubelet can manage pods independently by reading pod definition
+// files from a configured host directory (typically /etc/kubernetes/manifests).
+// Such pods are called *static pods* and are owned by a cluster Node.
+// In this case we treat them as unmanaged pods. (Otherwise we'd require
+// cluster-scoped permissions to get Nodes in order to set the owner reference
+// when we create an instance of custom security report.)
+//
+// TODO Merge this method with OwnerResolver, which accepts kube.Object and resolves client.Object.
 func GetImmediateOwnerReference(pod *corev1.Pod) kube.Object {
 	ownerRef := metav1.GetControllerOf(pod)
+
 	if ownerRef != nil {
+		if ownerRef.Kind == "Node" {
+			// Static pod ~ unmanaged pod
+			return kube.Object{
+				Kind:      kube.KindPod,
+				Namespace: pod.Namespace,
+				Name:      pod.Name,
+			}
+		}
+
 		return kube.Object{
 			Namespace: pod.Namespace,
 			Kind:      kube.Kind(ownerRef.Kind),
@@ -49,8 +69,8 @@ func GetImmediateOwnerReference(pod *corev1.Pod) kube.Object {
 		}
 	}
 	return kube.Object{
-		Namespace: pod.Namespace,
 		Kind:      kube.KindPod,
+		Namespace: pod.Namespace,
 		Name:      pod.Name,
 	}
 }


Test patch:
diff --git a/pkg/resources/resources_test.go b/pkg/resources/resources_test.go
index 1e47f10..30a796c 100644
--- a/pkg/resources/resources_test.go
+++ b/pkg/resources/resources_test.go
@@ -4,11 +4,90 @@ import (
 	"fmt"
 	"testing"
 
+	"github.com/aquasecurity/starboard/pkg/kube"
 	"github.com/aquasecurity/starboard/pkg/resources"
 	"github.com/stretchr/testify/assert"
 	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/utils/pointer"
 )
 
+func TestGetImmediateOwnerReference(t *testing.T) {
+
+	testCases := []struct {
+		name          string
+		pod           *corev1.Pod
+		expectedOwner kube.Object
+	}{
+		{
+			name: "Should return pod as owner of unmanaged pod",
+			pod: &corev1.Pod{
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace: "foo",
+					Name:      "unmanaged-pod",
+				},
+			},
+			expectedOwner: kube.Object{
+				Kind:      "Pod",
+				Namespace: "foo",
+				Name:      "unmanaged-pod",
+			},
+		},
+		{
+			name: "Should return ReplicaSet as owner of pod managed by Deployment",
+			pod: &corev1.Pod{
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace: "bar",
+					Name:      "nginx-6d4cf56db6-8g9j6",
+					OwnerReferences: []metav1.OwnerReference{
+						{
+							APIVersion: "apps/v1",
+							Kind:       "ReplicaSet",
+							Name:       "nginx-6d4cf56db6",
+							Controller: pointer.BoolPtr(true),
+						},
+					},
+				},
+			},
+			expectedOwner: kube.Object{
+				Kind:      "ReplicaSet",
+				Namespace: "bar",
+				Name:      "nginx-6d4cf56db6",
+			},
+		},
+		{
+			name: "Should return pod as owner of static pod managed by kubelet",
+			pod: &corev1.Pod{
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace: "kube-system",
+					Name:      "etcd-kind-control-plane",
+					OwnerReferences: []metav1.OwnerReference{
+						{
+							APIVersion: "v1",
+							Kind:       "Node",
+							Name:       "kind-control-plane",
+							Controller: pointer.BoolPtr(true),
+						},
+					},
+				},
+			},
+			expectedOwner: kube.Object{
+				Kind:      "Pod",
+				Namespace: "kube-system",
+				Name:      "etcd-kind-control-plane",
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			owner := resources.GetImmediateOwnerReference(tc.pod)
+			assert.Equal(t, tc.expectedOwner, owner)
+		})
+	}
+
+}
+
 func TestComputeHash(t *testing.T) {
 
 	booleanValue1 := true