Repository: FairwindsOps/polaris
Pull request #748 (2022-04-19 12:46:00)

Save last podspec when walking owner hierarchy
This PR fixes # https://github.com/FairwindsOps/polaris/issues/725

## Checklist
* [x] I have signed the CLA
* [x] I have updated/added any relevant documentation

## Description
### What's the goal of this PR?
Sometimes the ultimate owner of a Pod does not contain a `podSpec`. Examples include the Prometheus Operator's`Prometheus` CRD and Argo's `Rollout` CRD.

The goal here is to make sure we still capture pod-level findings for these resources.

### What changes did you make?
* Remove `isFinalKind`, which was an overly-specific heuristic for fixing this issue for the Prometheus CRD
* Track the most recent `podSpec` as we walk up the owner hierarchy. If we reach a resource that doesn't have a `podSpec`, use the one from its child

### What alternative solution should we consider, if any?
I think this is probably the best solution



Patch:
diff --git a/examples/config-full.yaml b/examples/config-full.yaml
index 31346f6a..11613cdd 100644
--- a/examples/config-full.yaml
+++ b/examples/config-full.yaml
@@ -87,5 +87,3 @@ customChecks:
           type: string
           not:
             pattern: ^quay.io
-
-namespce: test-ns
diff --git a/examples/config.yaml b/examples/config.yaml
index c9dfdf86..a06a2dac 100644
--- a/examples/config.yaml
+++ b/examples/config.yaml
@@ -202,5 +202,3 @@ exemptions:
       - kube-hunter
     rules:
       - runAsRootAllowed
-
-namespace: test-ns
diff --git a/pkg/kube/resource.go b/pkg/kube/resource.go
index ad739f7d..5c87f286 100644
--- a/pkg/kube/resource.go
+++ b/pkg/kube/resource.go
@@ -39,12 +39,11 @@ type GenericResource struct {
 }
 
 // NewGenericResourceFromUnstructured creates a workload from an unstructured.Unstructured
-func NewGenericResourceFromUnstructured(unst unstructured.Unstructured) (GenericResource, error) {
+func NewGenericResourceFromUnstructured(unst unstructured.Unstructured, podSpecMap interface{}) (GenericResource, error) {
 	workload := GenericResource{
 		Kind:     unst.GetKind(),
 		Resource: unst,
 	}
-
 	objMeta, err := meta.Accessor(&unst)
 	if err != nil {
 		return workload, err
@@ -61,7 +60,9 @@ func NewGenericResourceFromUnstructured(unst unstructured.Unstructured) (Generic
 	if err != nil {
 		return workload, err
 	}
-	podSpecMap := GetPodSpec(m)
+	if podSpecMap == nil {
+		podSpecMap = GetPodSpec(m)
+	}
 	if podSpecMap != nil {
 		b, err = json.Marshal(podSpecMap)
 		if err != nil {
@@ -113,7 +114,7 @@ func NewGenericResourceFromBytes(contentBytes []byte) (GenericResource, error) {
 	if err != nil {
 		return GenericResource{}, err
 	}
-	return NewGenericResourceFromUnstructured(unst)
+	return NewGenericResourceFromUnstructured(unst, nil)
 }
 
 // ResolveControllerFromPod builds a new workload for a given Pod
@@ -128,18 +129,6 @@ func ResolveControllerFromPod(ctx context.Context, podResource kubeAPICoreV1.Pod
 	return workload, err
 }
 
-func isFinalKind(kind string) bool {
-	switch kind {
-		case
-			"Deployment",
-			"CronJob",
-			"StatefulSet",
-			"DaemonSet":
-			return true
-		}
-	return false
-	}
-
 func resolveControllerFromPod(ctx context.Context, podResource kubeAPICoreV1.Pod, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) (GenericResource, error) {
 	podWorkload, err := NewGenericResourceFromPod(podResource, nil)
 	if err != nil {
@@ -147,6 +136,8 @@ func resolveControllerFromPod(ctx context.Context, podResource kubeAPICoreV1.Pod
 	}
 	topKind := "Pod"
 	topMeta := podWorkload.ObjectMeta
+	var topPodSpec interface{}
+	topPodSpec = podWorkload.Resource.Object
 	owners := podResource.ObjectMeta.GetOwnerReferences()
 	lastKey := ""
 	for len(owners) > 0 {
@@ -179,16 +170,17 @@ func resolveControllerFromPod(ctx context.Context, podResource kubeAPICoreV1.Pod
 			logrus.Warnf("Error retrieving parent metadata %s of API %s and Kind %s because of error: %v ", firstOwner.Name, firstOwner.APIVersion, firstOwner.Kind, err)
 			return GenericResource{}, err
 		}
+		podSpec := GetPodSpec(abstractObject.Object)
+		if podSpec != nil {
+			topPodSpec = podSpec
+		}
 		topMeta = objMeta
 		owners = abstractObject.GetOwnerReferences()
-		if isFinalKind(topKind) {
-			break
-		}
 	}
 
 	if lastKey != "" {
 		unst := objectCache[lastKey]
-		return NewGenericResourceFromUnstructured(unst)
+		return NewGenericResourceFromUnstructured(unst, topPodSpec)
 	}
 	workload, err := NewGenericResourceFromPod(podResource, podResource)
 	if err != nil {
diff --git a/pkg/kube/resources.go b/pkg/kube/resources.go
index 6d15749d..9c4ecca3 100644
--- a/pkg/kube/resources.go
+++ b/pkg/kube/resources.go
@@ -182,7 +182,7 @@ func CreateResourceProviderFromResource(ctx context.Context, workload string) (*
 		logrus.Errorf("Could not find workload %s: %v", workload, err)
 		return nil, err
 	}
-	workloadObj, err := NewGenericResourceFromUnstructured(*obj)
+	workloadObj, err := NewGenericResourceFromUnstructured(*obj, nil)
 	if err != nil {
 		logrus.Errorf("Could not parse workload %s: %v", workload, err)
 		return nil, err
@@ -335,7 +335,7 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
 			return nil, err
 		}
 		for _, obj := range objects.Items {
-			res, err := NewGenericResourceFromUnstructured(obj)
+			res, err := NewGenericResourceFromUnstructured(obj, nil)
 			if err != nil {
 				return nil, err
 			}


Test patch:
diff --git a/pkg/validator/arbitrary_test.go b/pkg/validator/arbitrary_test.go
index 9687d5cf..ae48dab2 100644
--- a/pkg/validator/arbitrary_test.go
+++ b/pkg/validator/arbitrary_test.go
@@ -33,7 +33,7 @@ func TestValidatePDB(t *testing.T) {
 		},
 	}
 	pdb := unstructured.Unstructured{}
-	res, err := kube.NewGenericResourceFromUnstructured(pdb)
+	res, err := kube.NewGenericResourceFromUnstructured(pdb, nil)
 	res.Kind = "PodDisruptionBudget"
 
 	actualResult, err := applyNonControllerSchemaChecks(&c, nil, res)
@@ -70,7 +70,7 @@ func TestValidateIngress(t *testing.T) {
 	if err != nil {
 		panic(err)
 	}
-	res, err := kube.NewGenericResourceFromUnstructured(unst)
+	res, err := kube.NewGenericResourceFromUnstructured(unst, nil)
 	if err != nil {
 		panic(err)
 	}