Repository: zegl/kube-score (go)

Issue:
Defining policyTypes should not be required in NetworkPolicy
**Which version of kube-score are you using?**

kube-score version: kube-score version: 1.2.1, commit: c108212e8f04339127cb40de0b619185f61aa348, built: 2019-08-20T10:27:04Z

**What did you do?**

Run kube-score on network policy without `policyTypes` defined. 

https://kubernetes.io/docs/concepts/services-networking/network-policies/
> If no policyTypes are specified on a NetworkPolicy then by default Ingress will always be set and Egress will be set if the NetworkPolicy has any egress rules.


```
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: testapp-netpol
  namespace: testspace
spec:
  podSelector:
    matchLabels:
      app: testapp
  ingress:
  - ports:
    - port: 53
      protocol: TCP
    - port: 53
      protocol: UDP
---
apiVersion: v1
kind: Pod
metadata:
  name: pod-test-1
  namespace: testspace
  labels:
    app: testapp
spec:
  containers:
  - name: foobar
    image: foo/bar:latest
```


**What did you expect to see?**

```
    [WARNING] Pod NetworkPolicy
        · The pod does not have a matching egress network policy
            Add a ingress policy to the pods NetworkPolicy
```
**What did you see instead?**

```
    [CRITICAL] Pod NetworkPolicy
        · The pod does not have a matching network policy
            Create a NetworkPolicy that targets this pod
```


Pull request description:
Handle default PolicyTypes in NetworkPolicies
This fixes #180

```
RELNOTE: Handle the default value of PolicyTypes in NetworkPolicies
```


Patch:
diff --git a/score/networkpolicy/networkpolicy.go b/score/networkpolicy/networkpolicy.go
index 4f8dcc0..e70105c 100644
--- a/score/networkpolicy/networkpolicy.go
+++ b/score/networkpolicy/networkpolicy.go
@@ -35,13 +35,32 @@ func podHasNetworkPolicy(allNetpols []networkingv1.NetworkPolicy) func(spec core
 			for labelKey, labelVal := range matchLabels {
 				if podLabelVal, ok := podSpec.Labels[labelKey]; ok && podLabelVal == labelVal {
 
-					for _, policyType := range netPol.Spec.PolicyTypes {
-						if policyType == networkingv1.PolicyTypeIngress {
-							hasMatchingIngressNetpol = true
-						}
-						if policyType == networkingv1.PolicyTypeEgress {
+					// Documentation of PolicyTypes
+					//
+					// List of rule types that the NetworkPolicy relates to.
+					// Valid options are "Ingress", "Egress", or "Ingress,Egress".
+					// If this field is not specified, it will default based on the existence of Ingress or Egress rules;
+					// policies that contain an Egress section are assumed to affect Egress, and all policies
+					// (whether or not they contain an Ingress section) are assumed to affect Ingress.
+					// If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ].
+					// Likewise, if you want to write a policy that specifies that no egress is allowed,
+					// you must specify a policyTypes value that include "Egress" (since such a policy would not include
+					// an Egress section and would otherwise default to just [ "Ingress" ]).
+
+					if netPol.Spec.PolicyTypes == nil || len(netPol.Spec.PolicyTypes) == 0 {
+						hasMatchingIngressNetpol = true
+						if len(netPol.Spec.Egress) > 0 {
 							hasMatchingEgressNetpol = true
 						}
+					} else {
+						for _, policyType := range netPol.Spec.PolicyTypes {
+							if policyType == networkingv1.PolicyTypeIngress {
+								hasMatchingIngressNetpol = true
+							}
+							if policyType == networkingv1.PolicyTypeEgress {
+								hasMatchingEgressNetpol = true
+							}
+						}
 					}
 				}
 			}


Test patch:
diff --git a/score/networkpolicy/networkpolicy_test.go b/score/networkpolicy/networkpolicy_test.go
new file mode 100644
index 0000000..9a5f877
--- /dev/null
+++ b/score/networkpolicy/networkpolicy_test.go
@@ -0,0 +1,105 @@
+package networkpolicy
+
+import (
+	"github.com/stretchr/testify/assert"
+	corev1 "k8s.io/api/core/v1"
+	v1 "k8s.io/api/networking/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"testing"
+
+	"github.com/zegl/kube-score/scorecard"
+)
+
+func TestPodHasNetworkPolicy(t *testing.T) {
+	cases := []struct {
+		polTypes    []v1.PolicyType
+		expected    scorecard.Grade
+		ingress     []v1.NetworkPolicyIngressRule
+		egress      []v1.NetworkPolicyEgressRule
+		selectorVal string
+	}{
+		{
+			polTypes:    []v1.PolicyType{v1.PolicyTypeIngress},
+			expected:    scorecard.GradeWarning, // has no egress
+			selectorVal: "test-a",
+		},
+		{
+			polTypes:    []v1.PolicyType{v1.PolicyTypeEgress},
+			expected:    scorecard.GradeWarning, // has no ingress
+			selectorVal: "test-a",
+		},
+		{
+			polTypes:    []v1.PolicyType{v1.PolicyTypeEgress, v1.PolicyTypeIngress},
+			expected:    scorecard.GradeAllOK,
+			selectorVal: "test-a",
+		},
+		{
+			polTypes:    []v1.PolicyType{},
+			expected:    scorecard.GradeWarning, // has no egress
+			selectorVal: "test-a",
+		},
+		{
+			polTypes:    nil,
+			expected:    scorecard.GradeWarning, // has no egress
+			selectorVal: "test-a",
+		},
+		{
+			polTypes:    []v1.PolicyType{},
+			egress:      []v1.NetworkPolicyEgressRule{{}, {}},
+			expected:    scorecard.GradeAllOK,
+			selectorVal: "test-a",
+		},
+		{
+			polTypes:    []v1.PolicyType{},
+			ingress:     []v1.NetworkPolicyIngressRule{{}, {}},
+			expected:    scorecard.GradeWarning, // has no ingress
+			selectorVal: "test-a",
+		},
+		{
+			polTypes:    []v1.PolicyType{},
+			ingress:     []v1.NetworkPolicyIngressRule{{}, {}},
+			egress:      []v1.NetworkPolicyEgressRule{{}, {}},
+			expected:    scorecard.GradeAllOK,
+			selectorVal: "test-a",
+		},
+		{
+			polTypes:    nil,
+			ingress:     []v1.NetworkPolicyIngressRule{{}, {}},
+			egress:      []v1.NetworkPolicyEgressRule{{}, {}},
+			expected:    scorecard.GradeAllOK,
+			selectorVal: "test-a",
+		},
+		{
+			polTypes:    []v1.PolicyType{v1.PolicyTypeEgress, v1.PolicyTypeIngress},
+			expected:    scorecard.GradeCritical, // pod has no ingress matching
+			selectorVal: "test-not-matching",
+		},
+	}
+
+	for caseID, tc := range cases {
+		pol := v1.NetworkPolicy{
+			Spec: v1.NetworkPolicySpec{
+				PodSelector: metav1.LabelSelector{
+					MatchLabels: map[string]string{"test": tc.selectorVal},
+				},
+				Ingress:     tc.ingress,
+				Egress:      tc.egress,
+				PolicyTypes: tc.polTypes,
+			},
+		}
+
+		pod := corev1.Pod{
+			TypeMeta: metav1.TypeMeta{},
+			ObjectMeta: metav1.ObjectMeta{
+				Labels: map[string]string{
+					"test": "test-a",
+				},
+			},
+			Spec: corev1.PodSpec{},
+		}
+
+		fn := podHasNetworkPolicy([]v1.NetworkPolicy{pol})
+		score := fn(corev1.PodTemplateSpec{ObjectMeta: pod.ObjectMeta, Spec: pod.Spec}, pod.TypeMeta)
+		assert.Equal(t, tc.expected, score.Grade, "caseID = %d", caseID)
+	}
+}