diff --git a/cmd/policy-assistant/examples/example.go b/cmd/policy-assistant/examples/example.go new file mode 100644 index 00000000..5b37d252 --- /dev/null +++ b/cmd/policy-assistant/examples/example.go @@ -0,0 +1,869 @@ +package examples + +import ( + v12 "k8s.io/api/core/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/network-policy-api/apis/v1alpha1" +) + +var CoreGressRulesCombinedANB = []*v1alpha1.AdminNetworkPolicy{ + { + ObjectMeta: v1.ObjectMeta{ + Name: "example-anp", + }, + Spec: v1alpha1.AdminNetworkPolicySpec{ + Priority: 20, + Subject: v1alpha1.AdminNetworkPolicySubject{ + Namespaces: &v1.LabelSelector{ + MatchExpressions: []v1.LabelSelectorRequirement{ + { + Key: "kubernetes.io/metadata.name", + Operator: v1.LabelSelectorOpExists, + }, + }, + }, + }, + Egress: []v1alpha1.AdminNetworkPolicyEgressRule{ + { + Name: "allow-to-ravenclaw-everything", + Action: v1alpha1.AdminNetworkPolicyRuleActionAllow, + To: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NamespaceSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "network-policy-conformance-ravenclaw", + }, + }, + }, + }, + }, + }, + { + Name: "deny-to-ravenclaw-everything", + Action: v1alpha1.AdminNetworkPolicyRuleActionDeny, + To: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NamespaceSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "network-policy-conformance-ravenclaw", + }, + }, + }, + }, + }, + }, + { + Name: "pass-to-ravenclaw-everything", + Action: v1alpha1.AdminNetworkPolicyRuleActionPass, + To: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NamespaceSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "network-policy-conformance-ravenclaw", + }, + }, + }, + }, + }, + }, + { + Name: "deny-to-slytherin-at-ports-80-53-9003", + Action: v1alpha1.AdminNetworkPolicyRuleActionDeny, + To: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NamespaceSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "network-policy-conformance-slytherin", + }, + }, + }, + }, + }, + Ports: &[]v1alpha1.AdminNetworkPolicyPort{ + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolTCP, Port: 80}, + }, + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolUDP, Port: 53}, + }, + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolSCTP, Port: 9003}, + }, + }, + }, + { + Name: "pass-to-slytherin-at-port-80-53-9003", + Action: v1alpha1.AdminNetworkPolicyRuleActionPass, + To: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NamespaceSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "network-policy-conformance-slytherin", + }, + }, + }, + }, + }, + Ports: &[]v1alpha1.AdminNetworkPolicyPort{ + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolTCP, Port: 80}, + }, + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolUDP, Port: 53}, + }, + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolSCTP, Port: 9003}, + }, + }, + }, + { + Name: "allow-to-hufflepuff-at-ports-8080-5353", + Action: v1alpha1.AdminNetworkPolicyRuleActionAllow, + To: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NamespaceSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "network-policy-conformance-hufflepuff", + }, + }, + }, + }, + }, + Ports: &[]v1alpha1.AdminNetworkPolicyPort{ + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolTCP, Port: 8080}, + }, + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolUDP, Port: 5353}, + }, + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolSCTP, Port: 9003}, + }, + }, + }, + { + Name: "deny-to-hufflepuff-everything-else", + Action: v1alpha1.AdminNetworkPolicyRuleActionDeny, + To: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NamespaceSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "network-policy-conformance-hufflepuff", + }, + }, + }, + }, + }, + }, + }, + Ingress: []v1alpha1.AdminNetworkPolicyIngressRule{ + { + Name: "allow-from-ravenclaw-everything", + Action: v1alpha1.AdminNetworkPolicyRuleActionAllow, + From: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NamespaceSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "network-policy-conformance-ravenclaw", + }, + }, + }, + }, + }, + }, + { + Name: "deny-from-ravenclaw-everything", + Action: v1alpha1.AdminNetworkPolicyRuleActionDeny, + From: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NamespaceSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "network-policy-conformance-ravenclaw", + }, + }, + }, + }, + }, + }, + { + Name: "pass-from-ravenclaw-everything", + Action: v1alpha1.AdminNetworkPolicyRuleActionPass, + From: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NamespaceSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "network-policy-conformance-ravenclaw", + }, + }, + }, + }, + }, + }, + { + Name: "deny-from-slytherin-at-port-80-53-9003", + Action: v1alpha1.AdminNetworkPolicyRuleActionDeny, + From: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NamespaceSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "network-policy-conformance-slytherin", + }, + }, + }, + }, + }, + Ports: &[]v1alpha1.AdminNetworkPolicyPort{ + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolTCP, Port: 80}, + }, + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolUDP, Port: 53}, + }, + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolSCTP, Port: 9003}, + }, + }, + }, + { + Name: "pass-from-slytherin-at-port-80-53-9003", + Action: v1alpha1.AdminNetworkPolicyRuleActionPass, + From: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NamespaceSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "network-policy-conformance-slytherin", + }, + }, + }, + }, + }, + Ports: &[]v1alpha1.AdminNetworkPolicyPort{ + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolTCP, Port: 80}, + }, + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolUDP, Port: 53}, + }, + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolSCTP, Port: 9003}, + }, + }, + }, + { + Name: "allow-from-hufflepuff-at-port-80-5353-9003", + Action: v1alpha1.AdminNetworkPolicyRuleActionAllow, + From: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NamespaceSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "network-policy-conformance-hufflepuff", + }, + }, + }, + }, + }, + Ports: &[]v1alpha1.AdminNetworkPolicyPort{ + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolTCP, Port: 80}, + }, + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolUDP, Port: 5353}, + }, + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolSCTP, Port: 9003}, + }, + }, + }, + { + Name: "deny-from-hufflepuff-everything-else", + Action: v1alpha1.AdminNetworkPolicyRuleActionDeny, + From: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NamespaceSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "network-policy-conformance-hufflepuff", + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + ObjectMeta: v1.ObjectMeta{ + Name: "example-anp-2", + }, + Spec: v1alpha1.AdminNetworkPolicySpec{ + Priority: 16, + Subject: v1alpha1.AdminNetworkPolicySubject{ + Namespaces: &v1.LabelSelector{ + MatchExpressions: []v1.LabelSelectorRequirement{ + { + Key: "kubernetes.io/metadata.name", + Operator: v1.LabelSelectorOpExists, + }, + }, + }, + }, + Egress: []v1alpha1.AdminNetworkPolicyEgressRule{ + { + Name: "allow-to-ravenclaw-everything-2", + Action: v1alpha1.AdminNetworkPolicyRuleActionAllow, + To: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NamespaceSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "network-policy-conformance-ravenclaw", + }, + }, + }, + }, + }, + }, + { + Name: "deny-to-ravenclaw-everything-2", + Action: v1alpha1.AdminNetworkPolicyRuleActionDeny, + To: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NamespaceSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "network-policy-conformance-ravenclaw", + }, + }, + }, + }, + }, + }, + { + Name: "pass-to-ravenclaw-everything-2", + Action: v1alpha1.AdminNetworkPolicyRuleActionPass, + To: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NamespaceSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "network-policy-conformance-ravenclaw", + }, + }, + }, + }, + }, + }, + { + Name: "deny-to-slytherin-at-ports-80-53-9003-2", + Action: v1alpha1.AdminNetworkPolicyRuleActionDeny, + To: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NamespaceSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "network-policy-conformance-slytherin", + }, + }, + }, + }, + }, + Ports: &[]v1alpha1.AdminNetworkPolicyPort{ + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolTCP, Port: 80}, + }, + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolUDP, Port: 53}, + }, + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolSCTP, Port: 9003}, + }, + }, + }, + { + Name: "pass-to-slytherin-at-port-80-53-9003-2", + Action: v1alpha1.AdminNetworkPolicyRuleActionPass, + To: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NamespaceSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "network-policy-conformance-slytherin", + }, + }, + }, + }, + }, + Ports: &[]v1alpha1.AdminNetworkPolicyPort{ + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolTCP, Port: 80}, + }, + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolUDP, Port: 53}, + }, + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolSCTP, Port: 9003}, + }, + }, + }, + { + Name: "allow-to-hufflepuff-at-ports-8080-5353-2", + Action: v1alpha1.AdminNetworkPolicyRuleActionAllow, + To: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NamespaceSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "network-policy-conformance-hufflepuff", + }, + }, + }, + }, + }, + Ports: &[]v1alpha1.AdminNetworkPolicyPort{ + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolTCP, Port: 8080}, + }, + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolUDP, Port: 5353}, + }, + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolSCTP, Port: 9003}, + }, + }, + }, + { + Name: "deny-to-hufflepuff-everything-else-2", + Action: v1alpha1.AdminNetworkPolicyRuleActionDeny, + To: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NamespaceSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "network-policy-conformance-hufflepuff", + }, + }, + }, + }, + }, + }, + }, + Ingress: []v1alpha1.AdminNetworkPolicyIngressRule{ + { + Name: "allow-from-ravenclaw-everything-2", + Action: v1alpha1.AdminNetworkPolicyRuleActionAllow, + From: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NamespaceSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "network-policy-conformance-ravenclaw", + }, + }, + }, + }, + }, + }, + { + Name: "deny-from-ravenclaw-everything-2", + Action: v1alpha1.AdminNetworkPolicyRuleActionDeny, + From: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NamespaceSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "network-policy-conformance-ravenclaw", + }, + }, + }, + }, + }, + }, + { + Name: "pass-from-ravenclaw-everything-2", + Action: v1alpha1.AdminNetworkPolicyRuleActionPass, + From: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NamespaceSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "network-policy-conformance-ravenclaw", + }, + }, + }, + }, + }, + }, + { + Name: "deny-from-slytherin-at-port-80-53-9003-2", + Action: v1alpha1.AdminNetworkPolicyRuleActionDeny, + From: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NamespaceSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "network-policy-conformance-slytherin", + }, + }, + }, + }, + }, + Ports: &[]v1alpha1.AdminNetworkPolicyPort{ + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolTCP, Port: 80}, + }, + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolUDP, Port: 53}, + }, + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolSCTP, Port: 9003}, + }, + }, + }, + { + Name: "pass-from-slytherin-at-port-80-53-9003-2", + Action: v1alpha1.AdminNetworkPolicyRuleActionPass, + From: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NamespaceSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "network-policy-conformance-slytherin", + }, + }, + }, + }, + }, + Ports: &[]v1alpha1.AdminNetworkPolicyPort{ + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolTCP, Port: 80}, + }, + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolUDP, Port: 53}, + }, + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolSCTP, Port: 9003}, + }, + }, + }, + { + Name: "allow-from-hufflepuff-at-port-80-5353-9003-2", + Action: v1alpha1.AdminNetworkPolicyRuleActionAllow, + From: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NamespaceSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "network-policy-conformance-hufflepuff", + }, + }, + }, + }, + }, + Ports: &[]v1alpha1.AdminNetworkPolicyPort{ + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolTCP, Port: 80}, + }, + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolUDP, Port: 5353}, + }, + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolSCTP, Port: 9003}, + }, + }, + }, + { + Name: "deny-from-hufflepuff-everything-else-2", + Action: v1alpha1.AdminNetworkPolicyRuleActionDeny, + From: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NamespaceSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "network-policy-conformance-hufflepuff", + }, + }, + }, + }, + }, + }, + }, + }, + }, +} + +var CoreGressRulesCombinedBANB *v1alpha1.BaselineAdminNetworkPolicy = &v1alpha1.BaselineAdminNetworkPolicy{ + ObjectMeta: v1.ObjectMeta{ + Name: "default", + }, + Spec: v1alpha1.BaselineAdminNetworkPolicySpec{ + Subject: v1alpha1.AdminNetworkPolicySubject{ + Namespaces: &v1.LabelSelector{ + MatchExpressions: []v1.LabelSelectorRequirement{ + { + Key: "kubernetes.io/metadata.name", + Operator: v1.LabelSelectorOpExists, + }, + }, + }, + }, + Egress: []v1alpha1.BaselineAdminNetworkPolicyEgressRule{ + { + Name: "allow-to-ravenclaw-everything", + Action: v1alpha1.BaselineAdminNetworkPolicyRuleActionAllow, + To: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + SameLabels: []string{"Test"}, + }, + }, + }, + }, + { + Name: "deny-to-ravenclaw-everything", + Action: v1alpha1.BaselineAdminNetworkPolicyRuleActionDeny, + To: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NotSameLabels: []string{"Test1"}, + }, + }, + }, + }, + { + Name: "deny-to-slytherin-at-ports-80-53-9003", + Action: v1alpha1.BaselineAdminNetworkPolicyRuleActionDeny, + To: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NamespaceSelector: &v1.LabelSelector{ + MatchExpressions: []v1.LabelSelectorRequirement{ + { + Key: "kubernetes.io/metadata.name", + Operator: v1.LabelSelectorOpExists, + }, + }, + }, + }, + }, + }, + Ports: &[]v1alpha1.AdminNetworkPolicyPort{ + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolTCP, Port: 80}, + }, + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolUDP, Port: 53}, + }, + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolSCTP, Port: 9003}, + }, + }, + }, + { + Name: "allow-to-hufflepuff-at-ports-8080-5353", + Action: v1alpha1.BaselineAdminNetworkPolicyRuleActionAllow, + To: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NamespaceSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "network-policy-conformance-hufflepuff", + }, + }, + }, + }, + }, + Ports: &[]v1alpha1.AdminNetworkPolicyPort{ + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolTCP, Port: 8080}, + }, + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolUDP, Port: 5353}, + }, + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolSCTP, Port: 9003}, + }, + }, + }, + { + Name: "deny-to-hufflepuff-everything-else", + Action: v1alpha1.BaselineAdminNetworkPolicyRuleActionDeny, + To: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NamespaceSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "network-policy-conformance-hufflepuff", + }, + }, + }, + }, + }, + }, + }, + Ingress: []v1alpha1.BaselineAdminNetworkPolicyIngressRule{ + { + Name: "allow-from-ravenclaw-everything", + Action: v1alpha1.BaselineAdminNetworkPolicyRuleActionAllow, + From: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NamespaceSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "network-policy-conformance-ravenclaw", + }, + }, + }, + }, + }, + }, + { + Name: "deny-from-slytherin-at-port-80-53-9003", + Action: v1alpha1.BaselineAdminNetworkPolicyRuleActionDeny, + From: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NamespaceSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "network-policy-conformance-slytherin", + }, + }, + }, + }, + }, + Ports: &[]v1alpha1.AdminNetworkPolicyPort{ + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolTCP, Port: 80}, + }, + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolUDP, Port: 53}, + }, + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolSCTP, Port: 9003}, + }, + }, + }, + { + Name: "allow-from-hufflepuff-at-port-80-5353-9003", + Action: v1alpha1.BaselineAdminNetworkPolicyRuleActionAllow, + From: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NamespaceSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "network-policy-conformance-hufflepuff", + }, + }, + }, + }, + }, + Ports: &[]v1alpha1.AdminNetworkPolicyPort{ + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolTCP, Port: 80}, + }, + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolUDP, Port: 5353}, + }, + { + PortNumber: &v1alpha1.Port{Protocol: v12.ProtocolSCTP, Port: 9003}, + }, + }, + }, + { + Name: "deny-from-hufflepuff-everything-else", + Action: v1alpha1.BaselineAdminNetworkPolicyRuleActionDeny, + From: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NamespaceSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "network-policy-conformance-hufflepuff", + }, + }, + }, + }, + }, + }, + }, + }, +} + +var SimpleANPs = []*v1alpha1.AdminNetworkPolicy{ + { + ObjectMeta: v1.ObjectMeta{ + Name: "simple-anp-1", + }, + Spec: v1alpha1.AdminNetworkPolicySpec{ + Priority: 34, + Subject: v1alpha1.AdminNetworkPolicySubject{ + Namespaces: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "test": "test", + }, + }, + }, + Egress: []v1alpha1.AdminNetworkPolicyEgressRule{ + { + Name: "allow-to-ravenclaw-everything", + Action: v1alpha1.AdminNetworkPolicyRuleActionAllow, + To: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NamespaceSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "network-policy-conformance-ravenclaw", + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + ObjectMeta: v1.ObjectMeta{ + Name: "simple-anp-2", + }, + Spec: v1alpha1.AdminNetworkPolicySpec{ + Priority: 50, + Subject: v1alpha1.AdminNetworkPolicySubject{ + Namespaces: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "test": "test", + }, + }, + }, + Egress: []v1alpha1.AdminNetworkPolicyEgressRule{ + { + Name: "allow-to-ravenclaw-everything", + Action: v1alpha1.AdminNetworkPolicyRuleActionDeny, + To: []v1alpha1.AdminNetworkPolicyPeer{ + { + Namespaces: &v1alpha1.NamespacedPeer{ + NamespaceSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "network-policy-conformance-ravenclaw", + }, + }, + }, + }, + }, + }, + }, + }, + }, +} diff --git a/cmd/policy-assistant/pkg/cli/analyze.go b/cmd/policy-assistant/pkg/cli/analyze.go index 0bd23930..1ec59156 100644 --- a/cmd/policy-assistant/pkg/cli/analyze.go +++ b/cmd/policy-assistant/pkg/cli/analyze.go @@ -2,6 +2,9 @@ package cli import ( "fmt" + "github.com/mattfenwick/cyclonus/examples" + "github.com/mattfenwick/cyclonus/pkg/kube/netpol" + "sigs.k8s.io/network-policy-api/apis/v1alpha1" "strings" "github.com/mattfenwick/collections/pkg/json" @@ -9,7 +12,6 @@ import ( "github.com/mattfenwick/cyclonus/pkg/generator" "github.com/mattfenwick/cyclonus/pkg/kube" - "github.com/mattfenwick/cyclonus/pkg/kube/netpol" "github.com/mattfenwick/cyclonus/pkg/matcher" "github.com/mattfenwick/cyclonus/pkg/utils" "github.com/pkg/errors" @@ -87,6 +89,8 @@ func SetupAnalyzeCommand() *cobra.Command { func RunAnalyzeCommand(args *AnalyzeArgs) { // 1. read policies from kube var kubePolicies []*networkingv1.NetworkPolicy + var kubeANPs []*v1alpha1.AdminNetworkPolicy + var kubeBANPs *v1alpha1.BaselineAdminNetworkPolicy var kubePods []v1.Pod var kubeNamespaces []v1.Namespace if args.AllNamespaces || len(args.Namespaces) > 0 { @@ -118,10 +122,13 @@ func RunAnalyzeCommand(args *AnalyzeArgs) { // 3. read example policies if args.UseExamplePolicies { kubePolicies = append(kubePolicies, netpol.AllExamples...) + + kubeANPs = examples.CoreGressRulesCombinedANB + kubeBANPs = examples.CoreGressRulesCombinedBANB } logrus.Debugf("parsed policies:\n%s", json.MustMarshalToString(kubePolicies)) - policies := matcher.BuildNetworkPolicies(args.SimplifyPolicies, kubePolicies) + policies := matcher.BuildV1AndV2NetPols(args.SimplifyPolicies, kubePolicies, kubeANPs, kubeBANPs) for _, mode := range args.Modes { switch mode { diff --git a/cmd/policy-assistant/pkg/kube/labelselector.go b/cmd/policy-assistant/pkg/kube/labelselector.go index 3cce2dd1..699fef6e 100644 --- a/cmd/policy-assistant/pkg/kube/labelselector.go +++ b/cmd/policy-assistant/pkg/kube/labelselector.go @@ -107,19 +107,17 @@ func LabelSelectorTableLines(selector metav1.LabelSelector) string { } var lines []string if len(selector.MatchLabels) > 0 { - lines = append(lines, "Match labels:") for _, key := range slice.Sort(maps.Keys(selector.MatchLabels)) { val := selector.MatchLabels[key] - lines = append(lines, fmt.Sprintf(" %s: %s", key, val)) + lines = append(lines, fmt.Sprintf(" %s = %s", key, val)) } } if len(selector.MatchExpressions) > 0 { - lines = append(lines, "Match expressions:") sortedMatchExpressions := slice.SortOn( func(l metav1.LabelSelectorRequirement) string { return l.Key }, selector.MatchExpressions) for _, exp := range sortedMatchExpressions { - lines = append(lines, fmt.Sprintf(" %s %s %+v", exp.Key, exp.Operator, slice.Sort(exp.Values))) + lines = append(lines, fmt.Sprintf(" %s %s %+v", exp.Key, exp.Operator, slice.Sort(exp.Values))) } } return strings.Join(lines, "\n") diff --git a/cmd/policy-assistant/pkg/matcher/builder.go b/cmd/policy-assistant/pkg/matcher/builder.go index b29ba7ab..d3b8919c 100644 --- a/cmd/policy-assistant/pkg/matcher/builder.go +++ b/cmd/policy-assistant/pkg/matcher/builder.go @@ -224,7 +224,7 @@ func BuildTargetANP(anp *v1alpha1.AdminNetworkPolicy) (*Target, *Target) { v := AdminActionToVerdict(r.Action) matchers := BuildPeerMatcherAdmin(r.From, r.Ports) for _, m := range matchers { - matcherAdmin := NewPeerMatcherANP(m, v, int(anp.Spec.Priority)) + matcherAdmin := NewPeerMatcherANP(m, v, int(anp.Spec.Priority), anp.Name) ingress.Peers = append(ingress.Peers, matcherAdmin) } } @@ -240,7 +240,7 @@ func BuildTargetANP(anp *v1alpha1.AdminNetworkPolicy) (*Target, *Target) { v := AdminActionToVerdict(r.Action) matchers := BuildPeerMatcherAdmin(r.To, r.Ports) for _, m := range matchers { - matcherAdmin := NewPeerMatcherANP(m, v, int(anp.Spec.Priority)) + matcherAdmin := NewPeerMatcherANP(m, v, int(anp.Spec.Priority), anp.Name) egress.Peers = append(egress.Peers, matcherAdmin) } } @@ -267,7 +267,7 @@ func BuildTargetBANP(banp *v1alpha1.BaselineAdminNetworkPolicy) (*Target, *Targe v := BaselineAdminActionToVerdict(r.Action) matchers := BuildPeerMatcherAdmin(r.From, r.Ports) for _, m := range matchers { - matcherAdmin := NewPeerMatcherBANP(m, v) + matcherAdmin := NewPeerMatcherBANP(m, v, banp.Name) ingress.Peers = append(ingress.Peers, matcherAdmin) } } @@ -283,7 +283,7 @@ func BuildTargetBANP(banp *v1alpha1.BaselineAdminNetworkPolicy) (*Target, *Targe v := BaselineAdminActionToVerdict(r.Action) matchers := BuildPeerMatcherAdmin(r.To, r.Ports) for _, m := range matchers { - matcherAdmin := NewPeerMatcherBANP(m, v) + matcherAdmin := NewPeerMatcherBANP(m, v, banp.Name) egress.Peers = append(egress.Peers, matcherAdmin) } } diff --git a/cmd/policy-assistant/pkg/matcher/builder_tests.go b/cmd/policy-assistant/pkg/matcher/builder_tests.go index 1f964b98..84ccd4df 100644 --- a/cmd/policy-assistant/pkg/matcher/builder_tests.go +++ b/cmd/policy-assistant/pkg/matcher/builder_tests.go @@ -1,9 +1,11 @@ package matcher import ( + "github.com/mattfenwick/cyclonus/examples" "github.com/mattfenwick/cyclonus/pkg/kube/netpol" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "golang.org/x/exp/maps" v1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -339,4 +341,14 @@ func RunBuilderTests() { }}})) }) }) + + Describe("BuildV1AndV2NetPols", func() { + It("it combines ANPs with same subject", func() { + result := BuildV1AndV2NetPols(true, nil, examples.SimpleANPs, nil) + Expect(result.Egress).To(HaveLen(1)) + k := maps.Keys(result.Egress) + firstRule := result.Egress[k[0]] + Expect(firstRule.SourceRules).To(HaveLen(2)) + }) + }) } diff --git a/cmd/policy-assistant/pkg/matcher/explain.go b/cmd/policy-assistant/pkg/matcher/explain.go index 0d74df52..3654d666 100644 --- a/cmd/policy-assistant/pkg/matcher/explain.go +++ b/cmd/policy-assistant/pkg/matcher/explain.go @@ -2,15 +2,36 @@ package matcher import ( "fmt" - "strings" - "github.com/mattfenwick/collections/pkg/json" "github.com/mattfenwick/collections/pkg/slice" + "golang.org/x/exp/slices" + v1 "k8s.io/api/core/v1" + "strings" + "github.com/mattfenwick/cyclonus/pkg/kube" "github.com/olekukonko/tablewriter" "github.com/pkg/errors" ) +// peerProtocolGroup groups all anps and banps in single struct +type peerProtocolGroup struct { + port string + subject string + policies map[string]*anpGroup +} + +// dummy implementation of the interface so we add the struct to the targer peers +func (p *peerProtocolGroup) Matches(subject, peer *TrafficPeer, portInt int, portName string, protocol v1.Protocol) bool { + return false +} + +type anpGroup struct { + name string + priority int + effects []string + kind PolicyKind +} + type SliceBuilder struct { Prefix []string Elements [][]string @@ -26,14 +47,14 @@ func (p *Policy) ExplainTable() string { table.SetAutoWrapText(false) table.SetRowLine(true) table.SetAutoMergeCells(true) - // FIXME add action/priority column - table.SetHeader([]string{"Type", "Subject", "Source rules", "Peer", "Port/Protocol"}) + + table.SetHeader([]string{"Type", "Subject", "Source rules", "Peer", "Action", "Port/Protocol"}) builder := &SliceBuilder{} ingresses, egresses := p.SortedTargets() builder.TargetsTableLines(ingresses, true) - // FIXME add action/priority column - builder.Elements = append(builder.Elements, []string{"", "", "", "", ""}) + + builder.Elements = append(builder.Elements, []string{"", "", "", "", "", ""}) builder.TargetsTableLines(egresses, false) table.AppendBulk(builder.Elements) @@ -50,68 +71,89 @@ func (s *SliceBuilder) TargetsTableLines(targets []*Target, isIngress bool) { ruleType = "Egress" } for _, target := range targets { - sourceRules := slice.Sort(target.SourceRules) + sourceRules := target.SourceRules sourceRulesStrings := make([]string, 0, len(sourceRules)) for _, rule := range sourceRules { sourceRulesStrings = append(sourceRulesStrings, string(rule)) } + slices.Sort(sourceRulesStrings) rules := strings.Join(sourceRulesStrings, "\n") s.Prefix = []string{ruleType, target.TargetString(), rules} if len(target.Peers) == 0 { - s.Append("no pods, no ips", "no ports, no protocols") - } else { - for _, peer := range slice.SortOn(func(p PeerMatcher) string { return json.MustMarshalToString(p) }, target.Peers) { - switch a := peer.(type) { - case *PeerMatcherAdmin: - s.PodPeerMatcherTableLines(a.PodPeerMatcher, a.effectFromMatch) - case *AllPeersMatcher: - s.Append("all pods, all ips", "all ports, all protocols") - case *PortsForAllPeersMatcher: - pps := PortMatcherTableLines(a.Port, NetworkPolicyV1) - s.Append("all pods, all ips", strings.Join(pps, "\n")) - case *IPPeerMatcher: - s.IPPeerMatcherTableLines(a) - case *PodPeerMatcher: - s.PodPeerMatcherTableLines(a, NewV1Effect(true)) - default: - panic(errors.Errorf("invalid PeerMatcher type %T", a)) - } + s.Append("no pods, no ips", "NPv1: All peers allowed", "no ports, no protocols") + continue + } + + peers := groupAnbAndBanp(target.Peers) + for _, p := range slice.SortOn(func(p PeerMatcher) string { return json.MustMarshalToString(p) }, peers) { + switch t := p.(type) { + case *AllPeersMatcher: + s.Append("all pods, all ips", "NPv1: All peers allowed", "all ports, all protocols") + case *PortsForAllPeersMatcher: + pps := PortMatcherTableLines(t.Port, NetworkPolicyV1) + s.Append("all pods, all ips", "NPv1: All peers allowed", strings.Join(pps, "\n")) + case *IPPeerMatcher: + s.IPPeerMatcherTableLines(t) + case *PodPeerMatcher: + s.Append(resolveSubject(t), "NPv1: All peers allowed", strings.Join(PortMatcherTableLines(t.Port, NewV1Effect(true).PolicyKind), "\n")) + case *peerProtocolGroup: + s.peerProtocolGroupTableLines(t) + default: + panic(errors.Errorf("invalid PeerMatcher type %T", p)) } } + } } func (s *SliceBuilder) IPPeerMatcherTableLines(ip *IPPeerMatcher) { peer := ip.IPBlock.CIDR + "\n" + fmt.Sprintf("except %+v", ip.IPBlock.Except) pps := PortMatcherTableLines(ip.Port, NetworkPolicyV1) - s.Append(peer, strings.Join(pps, "\n")) + s.Append(peer, "NPv1: All peers allowed", strings.Join(pps, "\n")) } -func (s *SliceBuilder) PodPeerMatcherTableLines(nsPodMatcher *PodPeerMatcher, e Effect) { - // FIXME add action/priority column using fields of the Effect parameter "e" - var namespaces string - switch ns := nsPodMatcher.Namespace.(type) { - case *AllNamespaceMatcher: - namespaces = "all" - case *LabelSelectorNamespaceMatcher: - namespaces = kube.LabelSelectorTableLines(ns.Selector) - // FIXME handle SameLabels, NotSameLabels - case *ExactNamespaceMatcher: - namespaces = ns.Namespace - default: - panic(errors.Errorf("invalid NamespaceMatcher type %T", ns)) +func (s *SliceBuilder) peerProtocolGroupTableLines(t *peerProtocolGroup) { + actions := []string{} + + anps := make([]*anpGroup, 0, len(t.policies)) + for _, v := range t.policies { + if v.kind == AdminNetworkPolicy { + anps = append(anps, v) + } } - var pods string - switch p := nsPodMatcher.Pod.(type) { - case *AllPodMatcher: - pods = "all" - case *LabelSelectorPodMatcher: - pods = kube.LabelSelectorTableLines(p.Selector) - default: - panic(errors.Errorf("invalid PodMatcher type %T", p)) + if len(anps) > 0 { + actions = append(actions, "ANP:") + slices.SortFunc(anps, func(a, b *anpGroup) bool { + return a.priority < b.priority + }) + for _, v := range anps { + if len(v.effects) > 1 { + actions = append(actions, fmt.Sprintf(" pri=%d (%s): %s (ineffective rules: %s)", v.priority, v.name, v.effects[0], strings.Join(v.effects[1:], ", "))) + } else { + actions = append(actions, fmt.Sprintf(" pri=%d (%s): %s", v.priority, v.name, v.effects[0])) + } + } + } + + banps := make([]*anpGroup, 0, len(t.policies)) + for _, v := range t.policies { + if v.kind == BaselineAdminNetworkPolicy { + banps = append(banps, v) + } + } + if len(banps) > 0 { + actions = append(actions, "BANP:") + for _, v := range banps { + if len(v.effects) > 1 { + actions = append(actions, fmt.Sprintf(" %s (ineffective rules: %s)", v.effects[0], strings.Join(v.effects[1:], ", "))) + } else { + actions = append(actions, fmt.Sprintf(" %s", v.effects[0])) + } + } } - s.Append("namespace: "+namespaces+"\n"+"pods: "+pods, strings.Join(PortMatcherTableLines(nsPodMatcher.Port, e.PolicyKind), "\n")) + + s.Append(t.subject, strings.Join(actions, "\n"), t.port) } func PortMatcherTableLines(pm PortMatcher, kind PolicyKind) []string { @@ -141,3 +183,81 @@ func PortMatcherTableLines(pm PortMatcher, kind PolicyKind) []string { panic(errors.Errorf("invalid PortMatcher type %T", port)) } } + +func groupAnbAndBanp(p []PeerMatcher) []PeerMatcher { + result := make([]PeerMatcher, 0, len(p)) + groups := map[string]*peerProtocolGroup{} + + for _, v := range p { + switch t := v.(type) { + case *PeerMatcherAdmin: + k := t.Port.GetPrimaryKey() + t.Pod.PrimaryKey() + t.Namespace.PrimaryKey() + if _, ok := groups[k]; !ok { + groups[k] = &peerProtocolGroup{ + port: strings.Join(PortMatcherTableLines(t.PodPeerMatcher.Port, t.effectFromMatch.PolicyKind), "\n"), + subject: resolveSubject(t.PodPeerMatcher), + policies: map[string]*anpGroup{}, + } + } + kg := t.Name + if _, ok := groups[k].policies[kg]; !ok { + groups[k].policies[kg] = &anpGroup{ + name: t.Name, + priority: t.effectFromMatch.Priority, + effects: []string{}, + kind: t.effectFromMatch.PolicyKind, + } + } + groups[k].policies[kg].effects = append(groups[k].policies[kg].effects, string(t.effectFromMatch.Verdict)) + default: + result = append(result, v) + } + } + + groupResult := make([]*peerProtocolGroup, 0, len(groups)) + for _, v := range groups { + groupResult = append(groupResult, v) + } + slices.SortFunc(groupResult, func(a, b *peerProtocolGroup) bool { + if a.port == b.port { + return a.subject < b.subject + } + return a.port < b.port + }) + + for _, v := range groupResult { + result = append(result, v) + } + + return result +} + +func resolveSubject(nsPodMatcher *PodPeerMatcher) string { + var namespaces string + var pods string + switch ns := nsPodMatcher.Namespace.(type) { + case *AllNamespaceMatcher: + namespaces = "all" + case *LabelSelectorNamespaceMatcher: + namespaces = kube.LabelSelectorTableLines(ns.Selector) + case *SameLabelsNamespaceMatcher: + namespaces = fmt.Sprintf("Same labels - %s", strings.Join(ns.labels, ", ")) + case *NotSameLabelsNamespaceMatcher: + namespaces = fmt.Sprintf("Not Same labels - %s", strings.Join(ns.labels, ", ")) + case *ExactNamespaceMatcher: + namespaces = ns.Namespace + default: + panic(errors.Errorf("invalid NamespaceMatcher type %T", ns)) + } + + switch p := nsPodMatcher.Pod.(type) { + case *AllPodMatcher: + pods = "all" + case *LabelSelectorPodMatcher: + pods = kube.LabelSelectorTableLines(p.Selector) + default: + panic(errors.Errorf("invalid PodMatcher type %T", p)) + } + + return fmt.Sprintf("Namespace:\n %s\nPod:\n %s", strings.TrimSpace(namespaces), strings.TrimSpace(pods)) +} diff --git a/cmd/policy-assistant/pkg/matcher/peermatcherv2.go b/cmd/policy-assistant/pkg/matcher/peermatcherv2.go index 9906831f..960359a4 100644 --- a/cmd/policy-assistant/pkg/matcher/peermatcherv2.go +++ b/cmd/policy-assistant/pkg/matcher/peermatcherv2.go @@ -10,13 +10,15 @@ import ( // This is because ANP and BANP only deal with Pod to Pod traffic, and do not deal with external IPs. type PeerMatcherAdmin struct { *PodPeerMatcher + Name string effectFromMatch Effect } // NewPeerMatcherANP creates a PeerMatcherAdmin for an ANP rule -func NewPeerMatcherANP(peer *PodPeerMatcher, v Verdict, priority int) *PeerMatcherAdmin { +func NewPeerMatcherANP(peer *PodPeerMatcher, v Verdict, priority int, source string) *PeerMatcherAdmin { return &PeerMatcherAdmin{ PodPeerMatcher: peer, + Name: source, effectFromMatch: Effect{ PolicyKind: AdminNetworkPolicy, Priority: priority, @@ -26,9 +28,10 @@ func NewPeerMatcherANP(peer *PodPeerMatcher, v Verdict, priority int) *PeerMatch } // NewPeerMatcherBANP creates a new PeerMatcherAdmin for a BANP rule -func NewPeerMatcherBANP(peer *PodPeerMatcher, v Verdict) *PeerMatcherAdmin { +func NewPeerMatcherBANP(peer *PodPeerMatcher, v Verdict, source string) *PeerMatcherAdmin { return &PeerMatcherAdmin{ PodPeerMatcher: peer, + Name: source, effectFromMatch: Effect{ PolicyKind: BaselineAdminNetworkPolicy, Verdict: v, diff --git a/cmd/policy-assistant/pkg/matcher/portmatcher.go b/cmd/policy-assistant/pkg/matcher/portmatcher.go index e69a3d8e..df2eda99 100644 --- a/cmd/policy-assistant/pkg/matcher/portmatcher.go +++ b/cmd/policy-assistant/pkg/matcher/portmatcher.go @@ -2,6 +2,7 @@ package matcher import ( "encoding/json" + "fmt" "sort" collectionsjson "github.com/mattfenwick/collections/pkg/json" @@ -12,6 +13,7 @@ import ( type PortMatcher interface { Matches(portInt int, portName string, protocol v1.Protocol) bool + GetPrimaryKey() string } type AllPortMatcher struct{} @@ -26,6 +28,10 @@ func (ap *AllPortMatcher) MarshalJSON() (b []byte, e error) { }) } +func (ap *AllPortMatcher) GetPrimaryKey() string { + return "all ports" +} + // PortProtocolMatcher models a matcher based on: // 1. Protocol // 2. Either a) port number or b) port name. @@ -62,6 +68,10 @@ func (p *PortProtocolMatcher) Equals(other *PortProtocolMatcher) bool { return isIntStringEqual(*p.Port, *other.Port) } +func (p *PortProtocolMatcher) GetPrimaryKey() string { + return fmt.Sprintf("Type: %s, Port: %s, Protocol: %s", "Port Protocol", p.Port.String(), p.Protocol) +} + // PortRangeMatcher works with endports to specify a range of matched numeric ports. type PortRangeMatcher struct { From int @@ -82,6 +92,10 @@ func (prm *PortRangeMatcher) MarshalJSON() (b []byte, e error) { }) } +func (prm *PortRangeMatcher) GetPrimaryKey() string { + return fmt.Sprintf("Type: %s, From: %d, To: %d, Protocol: %s", "port range", prm.From, prm.To, prm.Protocol) +} + // SpecificPortMatcher models the case where traffic must match a named or numbered port type SpecificPortMatcher struct { Ports []*PortProtocolMatcher @@ -110,6 +124,20 @@ func (s *SpecificPortMatcher) MarshalJSON() (b []byte, e error) { }) } +func (s *SpecificPortMatcher) GetPrimaryKey() string { + var p string + for _, v := range s.Ports { + p += v.GetPrimaryKey() + } + + var pr string + for _, v := range s.PortRanges { + pr += v.GetPrimaryKey() + } + + return fmt.Sprintf("Type: %s, Ports: %s, PortRanges: %s", "specific port", p, pr) +} + func (s *SpecificPortMatcher) Combine(other *SpecificPortMatcher) *SpecificPortMatcher { logrus.Debugf("SpecificPortMatcher Combined:\n%s\n", collectionsjson.MustMarshalToString([]interface{}{s, other})) diff --git a/cmd/policy-assistant/pkg/matcher/target.go b/cmd/policy-assistant/pkg/matcher/target.go index 9f2a686b..a3228884 100644 --- a/cmd/policy-assistant/pkg/matcher/target.go +++ b/cmd/policy-assistant/pkg/matcher/target.go @@ -2,12 +2,13 @@ package matcher import ( "fmt" - "github.com/mattfenwick/cyclonus/pkg/kube" "github.com/pkg/errors" networkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/network-policy-api/apis/v1alpha1" + "strings" ) // string of the form "[policyKind] namespace/name" @@ -72,7 +73,7 @@ func (t *Target) Combine(other *Target) *Target { return &Target{ SubjectMatcher: t.SubjectMatcher, Peers: append(t.Peers, other.Peers...), - SourceRules: append(t.SourceRules, other.SourceRules...), + SourceRules: sets.New(t.SourceRules...).Insert(other.SourceRules...).UnsortedList(), } } @@ -128,7 +129,7 @@ func (s *SubjectV1) TargetString() string { if pods == "all" { pods = "all pods" } - return fmt.Sprintf("namespace: %s\n%s", s.namespace, pods) + return fmt.Sprintf("Namespace:\n %s\nPod:\n %s", strings.TrimSpace(s.namespace), strings.TrimSpace(pods)) } func (s *SubjectV1) GetPrimaryKey() string { @@ -171,8 +172,14 @@ func (s *SubjectAdmin) Matches(candidate *InternalPeer) bool { } func (s *SubjectAdmin) TargetString() string { - // FIXME - return "FIXME: implement target string like v1's except it supports namespace selector and (not) same labels" + if s.subject.Namespaces != nil { + namespace := kube.LabelSelectorTableLines(*s.subject.Namespaces) + return fmt.Sprintf("Namespace:\n %s", strings.TrimSpace(namespace)) + } else { + namespace := kube.LabelSelectorTableLines(s.subject.Pods.NamespaceSelector) + pod := kube.LabelSelectorTableLines(s.subject.Pods.PodSelector) + return fmt.Sprintf("Namespace:\n %s\nPod:\n %s", strings.TrimSpace(namespace), strings.TrimSpace(pod)) + } } func (s *SubjectAdmin) GetPrimaryKey() string { diff --git a/cmd/policy-assistant/test/integration/explain_test.go b/cmd/policy-assistant/test/integration/explain_test.go new file mode 100644 index 00000000..f3bcb859 --- /dev/null +++ b/cmd/policy-assistant/test/integration/explain_test.go @@ -0,0 +1,172 @@ +package connectivity + +import ( + "github.com/mattfenwick/cyclonus/examples" + "github.com/mattfenwick/cyclonus/pkg/kube/netpol" + "github.com/mattfenwick/cyclonus/pkg/matcher" + "github.com/stretchr/testify/require" + "testing" +) + +func TestExplain(t *testing.T) { + t.Run("prints network policies v1", func(t *testing.T) { + expected := "+---------+--------------------+----------------------------------------------------------------------------+----------------------+-------------------------+---------------------------+\n" + + "| TYPE | SUBJECT | SOURCE RULES | PEER | ACTION | PORT/PROTOCOL |\n" + + "+---------+--------------------+----------------------------------------------------------------------------+----------------------+-------------------------+---------------------------+\n" + + "| Ingress | Namespace: | [NPv1] default/accidental-and | Namespace: | NPv1: All peers allowed | all ports, all protocols |\n" + + "| | default | [NPv1] default/accidental-or | default | | |\n" + + "| | Pod: | | Pod: | | |\n" + + "| | a = b | | role = client | | |\n" + + "+ + + +----------------------+ + +\n" + + "| | | | Namespace: | | |\n" + + "| | | | user = alice | | |\n" + + "| | | | Pod: | | |\n" + + "| | | | role = client | | |\n" + + "+ + + +----------------------+ + +\n" + + "| | | | Namespace: | | |\n" + + "| | | | user = alice | | |\n" + + "| | | | Pod: | | |\n" + + "| | | | all | | |\n" + + "+ +--------------------+----------------------------------------------------------------------------+----------------------+ +---------------------------+\n" + + "| | Namespace: | [NPv1] default/allow-nothing-to-v2-all-web | no pods, no ips | | no ports, no protocols |\n" + + "| | default | | | | |\n" + + "| | Pod: | | | | |\n" + + "| | all = web | | | | |\n" + + "+ +--------------------+----------------------------------------------------------------------------+----------------------+ +---------------------------+\n" + + "| | Namespace: | [NPv1] default/allow-specific-port-from-role-monitoring-to-app-apiserver | Namespace: | | port 5000 on protocol TCP |\n" + + "| | default | | default | | |\n" + + "| | Pod: | | Pod: | | |\n" + + "| | app = apiserver | | role = monitoring | | |\n" + + "+ +--------------------+----------------------------------------------------------------------------+----------------------+ +---------------------------+\n" + + "| | Namespace: | [NPv1] default/allow-from-app-bookstore-to-app-bookstore-role-api | Namespace: | | all ports, all protocols |\n" + + "| | default | | default | | |\n" + + "| | Pod: | | Pod: | | |\n" + + "| | app = bookstore | | app = bookstore | | |\n" + + "| | role = api | | | | |\n" + + "+ +--------------------+----------------------------------------------------------------------------+----------------------+ + +\n" + + "| | Namespace: | [NPv1] default/allow-from-multiple-to-app-bookstore-role-db | Namespace: | | |\n" + + "| | default | | default | | |\n" + + "| | Pod: | | Pod: | | |\n" + + "| | app = bookstore | | app = bookstore | | |\n" + + "| | role = db | | role = api | | |\n" + + "+ + + +----------------------+ + +\n" + + "| | | | Namespace: | | |\n" + + "| | | | default | | |\n" + + "| | | | Pod: | | |\n" + + "| | | | app = bookstore | | |\n" + + "| | | | role = search | | |\n" + + "+ + + +----------------------+ + +\n" + + "| | | | Namespace: | | |\n" + + "| | | | default | | |\n" + + "| | | | Pod: | | |\n" + + "| | | | app = inventory | | |\n" + + "| | | | role = web | | |\n" + + "+ +--------------------+----------------------------------------------------------------------------+----------------------+ +---------------------------+\n" + + "| | Namespace: | [NPv1] default/allow-nothing | no pods, no ips | | no ports, no protocols |\n" + + "| | default | | | | |\n" + + "| | Pod: | | | | |\n" + + "| | app = foo | | | | |\n" + + "+ +--------------------+----------------------------------------------------------------------------+----------------------+ +---------------------------+\n" + + "| | Namespace: | [NPv1] default/allow-all-to-app-web | all pods, all ips | | all ports, all protocols |\n" + + "| | default | [NPv1] default/allow-all-to-version2-app-web | | | |\n" + + "| | Pod: | [NPv1] default/allow-all-to-version3-app-web | | | |\n" + + "| | app = web | [NPv1] default/allow-all-to-version4-app-web | | | |\n" + + "| | | [NPv1] default/allow-from-anywhere-to-app-web | | | |\n" + + "| | | [NPv1] default/allow-from-namespace-to-app-web | | | |\n" + + "| | | [NPv1] default/allow-from-namespace-with-labels-type-monitoring-to-app-web | | | |\n" + + "| | | [NPv1] default/allow-nothing-to-app-web | | | |\n" + + "+ +--------------------+----------------------------------------------------------------------------+----------------------+ + +\n" + + "| | Namespace: | [NPv1] default/allow-all-within-namespace | Namespace: | | |\n" + + "| | default | [NPv1] default/allow-nothing-to-anything | default | | |\n" + + "| | Pod: | | Pod: | | |\n" + + "| | all pods | | all | | |\n" + + "+---------+--------------------+----------------------------------------------------------------------------+----------------------+-------------------------+---------------------------+\n" + + "| | | | | | |\n" + + "+---------+--------------------+----------------------------------------------------------------------------+----------------------+-------------------------+---------------------------+\n" + + "| Egress | Namespace: | [NPv1] default/allow-egress-on-port-app-foo | all pods, all ips | NPv1: All peers allowed | port 53 on protocol TCP |\n" + + "| | default | [NPv1] default/allow-egress-to-all-namespace-from-app-foo-on-port-53 | | | port 53 on protocol UDP |\n" + + "| | Pod: | [NPv1] default/allow-no-egress-from-labels-app-foo | | | |\n" + + "| | app = foo | [NPv1] default/allow-nothing | | | |\n" + + "+ +--------------------+----------------------------------------------------------------------------+----------------------+ +---------------------------+\n" + + "| | Namespace: | [NPv1] default/allow-no-egress-from-namespace | no pods, no ips | | no ports, no protocols |\n" + + "| | default | | | | |\n" + + "| | Pod: | | | | |\n" + + "| | all pods | | | | |\n" + + "+---------+--------------------+----------------------------------------------------------------------------+----------------------+-------------------------+---------------------------+\n" + + "" + policies := matcher.BuildV1AndV2NetPols(true, netpol.AllExamples, nil, nil) + require.Equal(t, expected, policies.ExplainTable()) + }) + + t.Run("prints network ANPs and BANPs", func(t *testing.T) { + expected := "+---------+------------------------------------------+-----------------------------+------------------------------------------------------------------------+------------------------------------------------------------------+----------------------------+\n" + + "| TYPE | SUBJECT | SOURCE RULES | PEER | ACTION | PORT/PROTOCOL |\n" + + "+---------+------------------------------------------+-----------------------------+------------------------------------------------------------------------+------------------------------------------------------------------+----------------------------+\n" + + "| Ingress | Namespace: | [ANP] default/example-anp | Namespace: | ANP: | all ports, all protocols |\n" + + "| | kubernetes.io/metadata.name Exists [] | [ANP] default/example-anp-2 | kubernetes.io/metadata.name = network-policy-conformance-hufflepuff | pri=16 (example-anp-2): Deny | |\n" + + "| | | [BANP] default/default | Pod: | pri=20 (example-anp): Deny | |\n" + + "| | | | all | BANP: | |\n" + + "| | | | | Deny | |\n" + + "+ + + +------------------------------------------------------------------------+------------------------------------------------------------------+ +\n" + + "| | | | Namespace: | ANP: | |\n" + + "| | | | kubernetes.io/metadata.name = network-policy-conformance-ravenclaw | pri=16 (example-anp-2): Allow (ineffective rules: Deny, Pass) | |\n" + + "| | | | Pod: | pri=20 (example-anp): Allow (ineffective rules: Deny, Pass) | |\n" + + "| | | | all | BANP: | |\n" + + "| | | | | Allow | |\n" + + "+ + + +------------------------------------------------------------------------+------------------------------------------------------------------+----------------------------+\n" + + "| | | | Namespace: | ANP: | port 80 on protocol TCP |\n" + + "| | | | kubernetes.io/metadata.name = network-policy-conformance-slytherin | pri=16 (example-anp-2): Deny (ineffective rules: Pass) | port 53 on protocol UDP |\n" + + "| | | | Pod: | pri=20 (example-anp): Deny (ineffective rules: Pass) | port 9003 on protocol SCTP |\n" + + "| | | | all | BANP: | |\n" + + "| | | | | Deny | |\n" + + "+ + + +------------------------------------------------------------------------+------------------------------------------------------------------+----------------------------+\n" + + "| | | | Namespace: | ANP: | port 80 on protocol TCP |\n" + + "| | | | kubernetes.io/metadata.name = network-policy-conformance-hufflepuff | pri=16 (example-anp-2): Allow | port 5353 on protocol UDP |\n" + + "| | | | Pod: | pri=20 (example-anp): Allow | port 9003 on protocol SCTP |\n" + + "| | | | all | BANP: | |\n" + + "| | | | | Allow | |\n" + + "+---------+------------------------------------------+-----------------------------+------------------------------------------------------------------------+------------------------------------------------------------------+----------------------------+\n" + + "| | | | | | |\n" + + "+---------+------------------------------------------+-----------------------------+------------------------------------------------------------------------+------------------------------------------------------------------+----------------------------+\n" + + "| Egress | Namespace: | [ANP] default/example-anp | Namespace: | BANP: | all ports, all protocols |\n" + + "| | kubernetes.io/metadata.name Exists [] | [ANP] default/example-anp-2 | Not Same labels - Test1 | Deny | |\n" + + "| | | [BANP] default/default | Pod: | | |\n" + + "| | | | all | | |\n" + + "+ + + +------------------------------------------------------------------------+------------------------------------------------------------------+ +\n" + + "| | | | Namespace: | BANP: | |\n" + + "| | | | Same labels - Test | Allow | |\n" + + "| | | | Pod: | | |\n" + + "| | | | all | | |\n" + + "+ + + +------------------------------------------------------------------------+------------------------------------------------------------------+ +\n" + + "| | | | Namespace: | ANP: | |\n" + + "| | | | kubernetes.io/metadata.name = network-policy-conformance-hufflepuff | pri=16 (example-anp-2): Deny | |\n" + + "| | | | Pod: | pri=20 (example-anp): Deny | |\n" + + "| | | | all | BANP: | |\n" + + "| | | | | Deny | |\n" + + "+ + + +------------------------------------------------------------------------+------------------------------------------------------------------+ +\n" + + "| | | | Namespace: | ANP: | |\n" + + "| | | | kubernetes.io/metadata.name = network-policy-conformance-ravenclaw | pri=16 (example-anp-2): Allow (ineffective rules: Deny, Pass) | |\n" + + "| | | | Pod: | pri=20 (example-anp): Allow (ineffective rules: Deny, Pass) | |\n" + + "| | | | all | | |\n" + + "+ + + +------------------------------------------------------------------------+------------------------------------------------------------------+----------------------------+\n" + + "| | | | Namespace: | ANP: | port 80 on protocol TCP |\n" + + "| | | | kubernetes.io/metadata.name = network-policy-conformance-slytherin | pri=16 (example-anp-2): Deny (ineffective rules: Pass) | port 53 on protocol UDP |\n" + + "| | | | Pod: | pri=20 (example-anp): Deny (ineffective rules: Pass) | port 9003 on protocol SCTP |\n" + + "| | | | all | | |\n" + + "+ + + +------------------------------------------------------------------------+------------------------------------------------------------------+ +\n" + + "| | | | Namespace: | BANP: | |\n" + + "| | | | kubernetes.io/metadata.name Exists [] | Deny | |\n" + + "| | | | Pod: | | |\n" + + "| | | | all | | |\n" + + "+ + + +------------------------------------------------------------------------+------------------------------------------------------------------+----------------------------+\n" + + "| | | | Namespace: | ANP: | port 8080 on protocol TCP |\n" + + "| | | | kubernetes.io/metadata.name = network-policy-conformance-hufflepuff | pri=16 (example-anp-2): Allow | port 5353 on protocol UDP |\n" + + "| | | | Pod: | pri=20 (example-anp): Allow | port 9003 on protocol SCTP |\n" + + "| | | | all | BANP: | |\n" + + "| | | | | Allow | |\n" + + "+---------+------------------------------------------+-----------------------------+------------------------------------------------------------------------+------------------------------------------------------------------+----------------------------+\n" + + "" + policies := matcher.BuildV1AndV2NetPols(false, nil, examples.CoreGressRulesCombinedANB, examples.CoreGressRulesCombinedBANB) + require.Equal(t, expected, policies.ExplainTable()) + }) +}