Repository: zegl/kube-score
Pull request #57 (2018-10-24 19:28:53)

networkpolicy: check if targets pod


Patch:
diff --git a/score/disruptionbudget/disruptionbudget.go b/score/disruptionbudget/disruptionbudget.go
index 173c993..e2da1d1 100644
--- a/score/disruptionbudget/disruptionbudget.go
+++ b/score/disruptionbudget/disruptionbudget.go
@@ -1,6 +1,7 @@
 package disruptionbudget
 
 import (
+	"github.com/zegl/kube-score/score/internal"
 	"github.com/zegl/kube-score/scorecard"
 
 	appsv1 "k8s.io/api/apps/v1"
@@ -19,7 +20,7 @@ func hasMatching(budgets []policyv1beta1.PodDisruptionBudget, namespace string,
 			panic(err)
 		}
 
-		if selector.Matches(mapLables(lables)) {
+		if selector.Matches(internal.MapLables(lables)) {
 			return true
 		}
 	}
@@ -58,14 +59,3 @@ func ScoreDeploymentHas(budgets []policyv1beta1.PodDisruptionBudget) func(appsv1
 		return
 	}
 }
-
-type mapLables map[string]string
-
-func (m mapLables) Has(key string) bool {
-	_, ok := m[key]
-	return ok
-}
-
-func (m mapLables) Get(key string) string {
-	return m[key]
-}
diff --git a/score/internal/labelselector.go b/score/internal/labelselector.go
new file mode 100644
index 0000000..3856c5b
--- /dev/null
+++ b/score/internal/labelselector.go
@@ -0,0 +1,13 @@
+package internal
+
+// Implements the Kubernetes Labels interface
+type MapLables map[string]string
+
+func (l MapLables) Has(key string) bool {
+	_, ok := l[key]
+	return ok
+}
+
+func (l MapLables) Get(key string) string {
+	return l[key]
+}
diff --git a/score/networkpolicy/networkpolicy.go b/score/networkpolicy/networkpolicy.go
index cca5702..2398133 100644
--- a/score/networkpolicy/networkpolicy.go
+++ b/score/networkpolicy/networkpolicy.go
@@ -1,9 +1,12 @@
 package networkpolicy
 
 import (
+	ks "github.com/zegl/kube-score"
+	"github.com/zegl/kube-score/score/internal"
 	"github.com/zegl/kube-score/scorecard"
 	corev1 "k8s.io/api/core/v1"
 	networkingv1 "k8s.io/api/networking/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
 // ScorePodHasNetworkPolicy returns a function that tests that all pods have matching NetworkPolicies
@@ -56,3 +59,49 @@ func ScorePodHasNetworkPolicy(allNetpols []networkingv1.NetworkPolicy) func(spec
 		return
 	}
 }
+
+func ScoreNetworkPolicyTargetsPod(pods []corev1.Pod, podspecers []ks.PodSpecer) func(networkingv1.NetworkPolicy) scorecard.TestScore {
+	return func(netpol networkingv1.NetworkPolicy) (score scorecard.TestScore) {
+		score.Name = "NetworkPolicy targets Pod"
+		score.ID = "networkpolicy-targets-pod"
+
+		hasMatch := false
+
+		for _, pod := range pods {
+			if pod.Namespace != netpol.Namespace {
+				continue
+			}
+
+			if selector, err := metav1.LabelSelectorAsSelector(&netpol.Spec.PodSelector); err == nil {
+				if selector.Matches(internal.MapLables(pod.Labels)) {
+					hasMatch = true
+					break
+				}
+			}
+		}
+
+		if !hasMatch {
+			for _, pod := range podspecers {
+				if pod.GetObjectMeta().Namespace != netpol.Namespace {
+					continue
+				}
+
+				if selector, err := metav1.LabelSelectorAsSelector(&netpol.Spec.PodSelector); err == nil {
+					if selector.Matches(internal.MapLables(pod.GetPodTemplateSpec().Labels)) {
+						hasMatch = true
+						break
+					}
+				}
+			}
+		}
+
+		if hasMatch {
+			score.Grade = scorecard.GradeAllOK
+		} else {
+			score.Grade = scorecard.GradeCritical
+			score.AddComment("", "The NetworkPolicys selector doesn't match any pods", "")
+		}
+
+		return
+	}
+}
diff --git a/score/score.go b/score/score.go
index 81fd290..c022072 100644
--- a/score/score.go
+++ b/score/score.go
@@ -67,7 +67,7 @@ type score struct {
 	typeMetas            []bothMeta
 	pods                 []corev1.Pod
 	podspecers           []ks.PodSpecer
-	networkPolies        []networkingv1.NetworkPolicy
+	networkPolicies      []networkingv1.NetworkPolicy
 	services             []corev1.Service
 	podDisruptionBudgets []policyv1beta1.PodDisruptionBudget
 	deployments          []appsv1.Deployment
@@ -193,7 +193,7 @@ func Score(config Configuration) (*scorecard.Scorecard, error) {
 			case networkingv1.SchemeGroupVersion.WithKind("NetworkPolicy"):
 				var netpol networkingv1.NetworkPolicy
 				decode(fileContents, &netpol)
-				s.networkPolies = append(s.networkPolies, netpol)
+				s.networkPolicies = append(s.networkPolicies, netpol)
 				s.typeMetas = append(s.typeMetas, bothMeta{netpol.TypeMeta, netpol.ObjectMeta})
 
 			case corev1.SchemeGroupVersion.WithKind("Service"):
@@ -228,7 +228,7 @@ func (s *score) runTests() (*scorecard.Scorecard, error) {
 		container.ScoreContainerLimits(!s.config.IgnoreContainerCpuLimitRequirement),
 		container.ScoreContainerImageTag,
 		container.ScoreContainerImagePullPolicy,
-		networkpolicy.ScorePodHasNetworkPolicy(s.networkPolies),
+		networkpolicy.ScorePodHasNetworkPolicy(s.networkPolicies),
 		probes.ScoreContainerProbes(s.services),
 		security.ScoreContainerSecurityContext,
 	}
@@ -245,6 +245,10 @@ func (s *score) runTests() (*scorecard.Scorecard, error) {
 		disruptionbudget.ScoreDeploymentHas(s.podDisruptionBudgets),
 	}
 
+	netpolTests := []func(policy networkingv1.NetworkPolicy) scorecard.TestScore{
+		networkpolicy.ScoreNetworkPolicyTargetsPod(s.pods, s.podspecers),
+	}
+
 	scoreCard := scorecard.New()
 
 	for _, meta := range s.typeMetas {
@@ -298,5 +302,13 @@ func (s *score) runTests() (*scorecard.Scorecard, error) {
 		}
 	}
 
+	for _, netpol := range s.networkPolicies {
+		for _, netpolTest := range netpolTests {
+			score := netpolTest(netpol)
+			score.AddMeta(netpol.TypeMeta, netpol.ObjectMeta)
+			scoreCard.Add(score)
+		}
+	}
+
 	return scoreCard, nil
 }
diff --git a/score/service/service.go b/score/service/service.go
index 3fd4bf1..950b8d3 100644
--- a/score/service/service.go
+++ b/score/service/service.go
@@ -10,14 +10,12 @@ import (
 // could be found
 func ScoreServiceTargetsPod(pods []corev1.Pod, podspecers []ks.PodSpecer) func(corev1.Service) scorecard.TestScore {
 	podsInNamespace := make(map[string][]map[string]string)
-
 	for _, pod := range pods {
 		if _, ok := podsInNamespace[pod.Namespace]; !ok {
 			podsInNamespace[pod.Namespace] = []map[string]string{}
 		}
 		podsInNamespace[pod.Namespace] = append(podsInNamespace[pod.Namespace], pod.Labels)
 	}
-
 	for _, podSpec := range podspecers {
 		if _, ok := podsInNamespace[podSpec.GetObjectMeta().Namespace]; !ok {
 			podsInNamespace[podSpec.GetObjectMeta().Namespace] = []map[string]string{}


Test patch:
diff --git a/score/networkpolicy_test.go b/score/networkpolicy_test.go
index 568a5e2..ab225c0 100644
--- a/score/networkpolicy_test.go
+++ b/score/networkpolicy_test.go
@@ -19,3 +19,15 @@ func TestPodHasMatchingIngressNetworkPolicy(t *testing.T) {
 func TestPodHasMatchingEgressNetworkPolicy(t *testing.T) {
 	testExpectedScore(t, "networkpolicy-matching-only-egress.yaml", "Pod NetworkPolicy", 5)
 }
+
+func TestNetworkPolicyTargetsPod(t *testing.T) {
+	testExpectedScore(t, "networkpolicy-targets-pod.yaml", "NetworkPolicy targets Pod", 10)
+}
+
+func TestNetworkPolicyTargetsPodInDeployment(t *testing.T) {
+	testExpectedScore(t, "networkpolicy-targets-pod-deployment.yaml", "NetworkPolicy targets Pod", 10)
+}
+
+func TestNetworkPolicyTargetsPodNotMatching(t *testing.T) {
+	testExpectedScore(t, "networkpolicy-targets-pod-not-matching.yaml", "NetworkPolicy targets Pod", 1)
+}
diff --git a/score/testdata/networkpolicy-targets-pod-deployment.yaml b/score/testdata/networkpolicy-targets-pod-deployment.yaml
new file mode 100644
index 0000000..c4e4188
--- /dev/null
+++ b/score/testdata/networkpolicy-targets-pod-deployment.yaml
@@ -0,0 +1,32 @@
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+  name: testapp-netpol
+  namespace: testspace
+spec:
+  podSelector:
+    matchLabels:
+      app: foo
+  egress:
+  - ports:
+    - port: 53
+      protocol: TCP
+    - port: 53
+      protocol: UDP
+  policyTypes:
+  - Egress
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: statefulset-test-1
+  namespace: testspace
+spec:
+  template:
+    metadata:
+      labels:
+        app: foo
+    spec:
+      containers:
+      - name: foobar
+        image: foo:bar
\ No newline at end of file
diff --git a/score/testdata/networkpolicy-targets-pod-not-matching.yaml b/score/testdata/networkpolicy-targets-pod-not-matching.yaml
new file mode 100644
index 0000000..3d1ccaa
--- /dev/null
+++ b/score/testdata/networkpolicy-targets-pod-not-matching.yaml
@@ -0,0 +1,29 @@
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+  name: testapp-netpol
+  namespace: testspace
+spec:
+  podSelector:
+    matchLabels:
+      app: not-testapp
+  egress:
+  - ports:
+    - port: 53
+      protocol: TCP
+    - port: 53
+      protocol: UDP
+  policyTypes:
+  - Egress
+---
+apiVersion: v1
+kind: Pod
+metadata:
+  name: pod-test-1
+  namespace: testspace
+  labels:
+    app: testapp
+spec:
+  containers:
+  - name: foobar
+    image: foo/bar:latest
diff --git a/score/testdata/networkpolicy-targets-pod.yaml b/score/testdata/networkpolicy-targets-pod.yaml
new file mode 100644
index 0000000..61d2975
--- /dev/null
+++ b/score/testdata/networkpolicy-targets-pod.yaml
@@ -0,0 +1,29 @@
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+  name: testapp-netpol
+  namespace: testspace
+spec:
+  podSelector:
+    matchLabels:
+      app: testapp
+  egress:
+  - ports:
+    - port: 53
+      protocol: TCP
+    - port: 53
+      protocol: UDP
+  policyTypes:
+  - Egress
+---
+apiVersion: v1
+kind: Pod
+metadata:
+  name: pod-test-1
+  namespace: testspace
+  labels:
+    app: testapp
+spec:
+  containers:
+  - name: foobar
+    image: foo/bar:latest