From 0a9d2958c7c4bb4add4cb3c188aa1d9d00c881f8 Mon Sep 17 00:00:00 2001 From: Nikola Date: Sat, 13 Jan 2024 16:29:57 +0200 Subject: [PATCH 1/4] add support for anp and banp in the explain command --- cmd/policy-assistant/examples/example.go | 506 ++++++++++++++++++ cmd/policy-assistant/pkg/cli/analyze.go | 11 +- .../pkg/kube/labelselector.go | 4 +- cmd/policy-assistant/pkg/matcher/builder.go | 8 +- cmd/policy-assistant/pkg/matcher/explain.go | 49 +- .../pkg/matcher/peermatcherv2.go | 7 +- cmd/policy-assistant/pkg/matcher/target.go | 7 +- 7 files changed, 562 insertions(+), 30 deletions(-) create mode 100644 cmd/policy-assistant/examples/example.go diff --git a/cmd/policy-assistant/examples/example.go b/cmd/policy-assistant/examples/example.go new file mode 100644 index 00000000..c8974ac3 --- /dev/null +++ b/cmd/policy-assistant/examples/example.go @@ -0,0 +1,506 @@ +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 = []*v1alpha1.AdminNetworkPolicy{ + { + ObjectMeta: v1.ObjectMeta{ + Name: "default", + }, + Spec: v1alpha1.AdminNetworkPolicySpec{ + Priority: 15, + Subject: v1alpha1.AdminNetworkPolicySubject{ + Namespaces: &v1.LabelSelector{ + MatchExpressions: []v1.LabelSelectorRequirement{ + { + Key: "kubernetes.io/metadata.name", + Operator: v1.LabelSelectorOpExists, + Values: []string{"network-policy-conformance-gryffindor"}, + }, + }, + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "", + }, + }, + }, + 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", + }, + }, + }, + }, + }, + }, + }, + }, + }, +} + +var CoreGressRulesCombinedBANB *v1alpha1.BaselineAdminNetworkPolicy = &v1alpha1.BaselineAdminNetworkPolicy{ + ObjectMeta: v1.ObjectMeta{ + Name: "default", + }, + Spec: v1alpha1.BaselineAdminNetworkPolicySpec{ + Subject: v1alpha1.AdminNetworkPolicySubject{ + Namespaces: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": "network-policy-conformance-gryffindor", + }, + }, + }, + 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, + Values: []string{"network-policy-conformance-gryffindor"}, + }, + }, + }, + }, + }, + }, + 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", + }, + }, + }, + }, + }, + }, + }, + }, +} 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..e378965c 100644 --- a/cmd/policy-assistant/pkg/kube/labelselector.go +++ b/cmd/policy-assistant/pkg/kube/labelselector.go @@ -107,14 +107,12 @@ 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) diff --git a/cmd/policy-assistant/pkg/matcher/builder.go b/cmd/policy-assistant/pkg/matcher/builder.go index b29ba7ab..5eaeffd9 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), r.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), r.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, r.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, r.Name) egress.Peers = append(egress.Peers, matcherAdmin) } } diff --git a/cmd/policy-assistant/pkg/matcher/explain.go b/cmd/policy-assistant/pkg/matcher/explain.go index 0d74df52..ba6880fc 100644 --- a/cmd/policy-assistant/pkg/matcher/explain.go +++ b/cmd/policy-assistant/pkg/matcher/explain.go @@ -2,9 +2,9 @@ package matcher import ( "fmt" + "github.com/mattfenwick/collections/pkg/json" "strings" - "github.com/mattfenwick/collections/pkg/json" "github.com/mattfenwick/collections/pkg/slice" "github.com/mattfenwick/cyclonus/pkg/kube" "github.com/olekukonko/tablewriter" @@ -26,14 +26,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,7 +50,7 @@ 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)) @@ -59,23 +59,23 @@ func (s *SliceBuilder) TargetsTableLines(targets []*Target, isIngress bool) { s.Prefix = []string{ruleType, target.TargetString(), rules} if len(target.Peers) == 0 { - s.Append("no pods, no ips", "no ports, no protocols") + s.Append("no pods, no ips", "no actions", "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) + s.PodPeerMatcherTableLines(a.PodPeerMatcher, a.effectFromMatch, a.Name) case *AllPeersMatcher: - s.Append("all pods, all ips", "all ports, all protocols") + s.Append("all pods, all ips", "NPv1: All peers allowed", "all ports, all protocols") case *PortsForAllPeersMatcher: pps := PortMatcherTableLines(a.Port, NetworkPolicyV1) - s.Append("all pods, all ips", strings.Join(pps, "\n")) + s.Append("all pods, all ips", "", strings.Join(pps, "\n")) case *IPPeerMatcher: s.IPPeerMatcherTableLines(a) case *PodPeerMatcher: - s.PodPeerMatcherTableLines(a, NewV1Effect(true)) + s.PodPeerMatcherTableLines(a, NewV1Effect(true), "") default: - panic(errors.Errorf("invalid PeerMatcher type %T", a)) + continue } } } @@ -85,18 +85,20 @@ func (s *SliceBuilder) TargetsTableLines(targets []*Target, isIngress bool) { 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, "", strings.Join(pps, "\n")) } -func (s *SliceBuilder) PodPeerMatcherTableLines(nsPodMatcher *PodPeerMatcher, e Effect) { - // FIXME add action/priority column using fields of the Effect parameter "e" +func (s *SliceBuilder) PodPeerMatcherTableLines(nsPodMatcher *PodPeerMatcher, e Effect, name string) { var namespaces string switch ns := nsPodMatcher.Namespace.(type) { case *AllNamespaceMatcher: namespaces = "all" case *LabelSelectorNamespaceMatcher: namespaces = kube.LabelSelectorTableLines(ns.Selector) - // FIXME handle SameLabels, NotSameLabels + 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: @@ -111,7 +113,7 @@ func (s *SliceBuilder) PodPeerMatcherTableLines(nsPodMatcher *PodPeerMatcher, e default: panic(errors.Errorf("invalid PodMatcher type %T", p)) } - s.Append("namespace: "+namespaces+"\n"+"pods: "+pods, strings.Join(PortMatcherTableLines(nsPodMatcher.Port, e.PolicyKind), "\n")) + s.Append(fmt.Sprintf("Namespace:\n %s\nPod:\n %s", strings.TrimSpace(namespaces), strings.TrimSpace(pods)), priorityTableLine(e, name), strings.Join(PortMatcherTableLines(nsPodMatcher.Port, e.PolicyKind), "\n")) } func PortMatcherTableLines(pm PortMatcher, kind PolicyKind) []string { @@ -141,3 +143,16 @@ func PortMatcherTableLines(pm PortMatcher, kind PolicyKind) []string { panic(errors.Errorf("invalid PortMatcher type %T", port)) } } + +func priorityTableLine(e Effect, name string) string { + if e.PolicyKind == NetworkPolicyV1 { + return "NPv1: All peers allowed" + } else if e.PolicyKind == AdminNetworkPolicy { + return fmt.Sprintf("%s (%s): %s (pri=%d)", e.PolicyKind, name, e.Verdict, e.Priority) + } else if e.PolicyKind == BaselineAdminNetworkPolicy { + return fmt.Sprintf("%s (%s): %s", e.PolicyKind, name, e.Verdict) + } else { + panic(errors.Errorf("Invalid effect %s", e.PolicyKind)) + } + +} 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/target.go b/cmd/policy-assistant/pkg/matcher/target.go index 9f2a686b..43fd65a8 100644 --- a/cmd/policy-assistant/pkg/matcher/target.go +++ b/cmd/policy-assistant/pkg/matcher/target.go @@ -171,8 +171,11 @@ 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 { + return fmt.Sprintf("Namespace labels:\n%s", kube.LabelSelectorTableLines(*s.subject.Namespaces)) + } else { + return fmt.Sprintf("Namespace labels:\n%s\nPod labels:\n%s", kube.LabelSelectorTableLines(s.subject.Pods.NamespaceSelector), kube.LabelSelectorTableLines(s.subject.Pods.PodSelector)) + } } func (s *SubjectAdmin) GetPrimaryKey() string { From f9b3cab757a7a6e792212202a0a7fc44a1c81ede Mon Sep 17 00:00:00 2001 From: Nikola Date: Sat, 24 Feb 2024 16:01:52 +0200 Subject: [PATCH 2/4] change the way the anps and banps are shown in the explain commnad --- cmd/policy-assistant/examples/example.go | 415 +++++++++++++++++- cmd/policy-assistant/pkg/matcher/builder.go | 60 ++- .../pkg/matcher/builder_tests.go | 82 +++- cmd/policy-assistant/pkg/matcher/explain.go | 166 +++++-- cmd/policy-assistant/pkg/matcher/policy.go | 28 +- .../pkg/matcher/portmatcher.go | 28 ++ cmd/policy-assistant/pkg/matcher/target.go | 71 ++- 7 files changed, 751 insertions(+), 99 deletions(-) diff --git a/cmd/policy-assistant/examples/example.go b/cmd/policy-assistant/examples/example.go index c8974ac3..34100450 100644 --- a/cmd/policy-assistant/examples/example.go +++ b/cmd/policy-assistant/examples/example.go @@ -6,13 +6,13 @@ import ( "sigs.k8s.io/network-policy-api/apis/v1alpha1" ) -var CoreGressRulesCombinedANB []*v1alpha1.AdminNetworkPolicy = []*v1alpha1.AdminNetworkPolicy{ +var CoreGressRulesCombinedANB = []*v1alpha1.AdminNetworkPolicy{ { ObjectMeta: v1.ObjectMeta{ - Name: "default", + Name: "Example ANP", }, Spec: v1alpha1.AdminNetworkPolicySpec{ - Priority: 15, + Priority: 20, Subject: v1alpha1.AdminNetworkPolicySubject{ Namespaces: &v1.LabelSelector{ MatchExpressions: []v1.LabelSelectorRequirement{ @@ -22,9 +22,6 @@ var CoreGressRulesCombinedANB []*v1alpha1.AdminNetworkPolicy = []*v1alpha1.Admin Values: []string{"network-policy-conformance-gryffindor"}, }, }, - MatchLabels: map[string]string{ - "kubernetes.io/metadata.name": "", - }, }, }, Egress: []v1alpha1.AdminNetworkPolicyEgressRule{ @@ -309,6 +306,305 @@ var CoreGressRulesCombinedANB []*v1alpha1.AdminNetworkPolicy = []*v1alpha1.Admin }, }, }, + { + 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, + Values: []string{"network-policy-conformance-gryffindor"}, + }, + }, + }, + }, + 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{ @@ -318,8 +614,12 @@ var CoreGressRulesCombinedBANB *v1alpha1.BaselineAdminNetworkPolicy = &v1alpha1. Spec: v1alpha1.BaselineAdminNetworkPolicySpec{ Subject: v1alpha1.AdminNetworkPolicySubject{ Namespaces: &v1.LabelSelector{ - MatchLabels: map[string]string{ - "kubernetes.io/metadata.name": "network-policy-conformance-gryffindor", + MatchExpressions: []v1.LabelSelectorRequirement{ + { + Key: "kubernetes.io/metadata.name", + Operator: v1.LabelSelectorOpExists, + Values: []string{"network-policy-conformance-gryffindor"}, + }, }, }, }, @@ -504,3 +804,102 @@ var CoreGressRulesCombinedBANB *v1alpha1.BaselineAdminNetworkPolicy = &v1alpha1. }, }, } + +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", + }, + }, + }, + }, + }, + }, + }, + }, + }, +} + +var SimpleBANP *v1alpha1.BaselineAdminNetworkPolicy = &v1alpha1.BaselineAdminNetworkPolicy{ + ObjectMeta: v1.ObjectMeta{ + Name: "Simple BANP", + }, + Spec: v1alpha1.BaselineAdminNetworkPolicySpec{ + Subject: v1alpha1.AdminNetworkPolicySubject{ + Namespaces: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "test": "test", + }, + }, + }, + Egress: []v1alpha1.BaselineAdminNetworkPolicyEgressRule{ + { + Name: "allow-to-ravenclaw-everything", + Action: v1alpha1.BaselineAdminNetworkPolicyRuleActionAllow, + 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/matcher/builder.go b/cmd/policy-assistant/pkg/matcher/builder.go index 5eaeffd9..f44a1f59 100644 --- a/cmd/policy-assistant/pkg/matcher/builder.go +++ b/cmd/policy-assistant/pkg/matcher/builder.go @@ -21,21 +21,30 @@ func BuildV1AndV2NetPols(simplify bool, netpols []*networkingv1.NetworkPolicy, a np.AddTarget(false, egress) } + var banpIngress *Target + var banpEgress *Target + + if banp != nil { + // there can only be one BANP by definition + banpIngress, banpEgress = BuildTargetBANP(banp) + np.AddTarget(true, banpIngress) + np.AddTarget(false, banpEgress) + } + priorities := make(map[int32]struct{}) for _, p := range anps { if _, ok := priorities[p.Spec.Priority]; ok { - panic(errors.Errorf("duplicate priorities are undefined. priority: %d", p.Spec.Priority)) + panic(errors.Errorf("duplicate priorities are now allowed. priority: %d", p.Spec.Priority)) } priorities[p.Spec.Priority] = struct{}{} ingress, egress := BuildTargetANP(p) - np.AddTarget(true, ingress) - np.AddTarget(false, egress) - } + if banpIngress != nil && ingress.GetPrimaryKey() == banpIngress.GetPrimaryKey() { + ingress.CombineCommonPeers(banpIngress) + egress.CombineCommonPeers(banpEgress) + + } - if banp != nil { - // there can only be one BANP by definition - ingress, egress := BuildTargetBANP(banp) np.AddTarget(true, ingress) np.AddTarget(false, egress) } @@ -64,16 +73,27 @@ func BuildTarget(netpol *networkingv1.NetworkPolicy) (*Target, *Target) { for _, pType := range netpol.Spec.PolicyTypes { switch pType { case networkingv1.PolicyTypeIngress: + p := map[string][]PeerMatcher{} + ingressPeers := BuildIngressMatcher(policyNamespace, netpol.Spec.Ingress) + if len(ingressPeers) > 0 { + p[""] = ingressPeers + } + ingress = &Target{ SubjectMatcher: NewSubjectV1(policyNamespace, netpol.Spec.PodSelector), SourceRules: []NetPolID{netPolID(netpol)}, - Peers: BuildIngressMatcher(policyNamespace, netpol.Spec.Ingress), + Peers: p, } case networkingv1.PolicyTypeEgress: + p := map[string][]PeerMatcher{} + egressPeers := BuildEgressMatcher(policyNamespace, netpol.Spec.Egress) + if len(egressPeers) > 0 { + p[""] = egressPeers + } egress = &Target{ SubjectMatcher: NewSubjectV1(policyNamespace, netpol.Spec.PodSelector), SourceRules: []NetPolID{netPolID(netpol)}, - Peers: BuildEgressMatcher(policyNamespace, netpol.Spec.Egress), + Peers: p, } } } @@ -218,34 +238,36 @@ func BuildTargetANP(anp *v1alpha1.AdminNetworkPolicy) (*Target, *Target) { ingress = &Target{ SubjectMatcher: NewSubjectAdmin(&anp.Spec.Subject), SourceRules: []NetPolID{netPolID(anp)}, + Peers: make(map[string][]PeerMatcher), } for _, r := range anp.Spec.Ingress { v := AdminActionToVerdict(r.Action) matchers := BuildPeerMatcherAdmin(r.From, r.Ports) for _, m := range matchers { - matcherAdmin := NewPeerMatcherANP(m, v, int(anp.Spec.Priority), r.Name) - ingress.Peers = append(ingress.Peers, matcherAdmin) + matcherAdmin := NewPeerMatcherANP(m, v, int(anp.Spec.Priority), anp.Name) + k := m.Pod.PrimaryKey() + m.Namespace.PrimaryKey() + m.Port.GetPrimaryKey() + ingress.Peers[k] = append(ingress.Peers[k], matcherAdmin) } } } - if len(anp.Spec.Egress) > 0 { egress = &Target{ SubjectMatcher: NewSubjectAdmin(&anp.Spec.Subject), SourceRules: []NetPolID{netPolID(anp)}, + Peers: make(map[string][]PeerMatcher), } for _, r := range anp.Spec.Egress { v := AdminActionToVerdict(r.Action) matchers := BuildPeerMatcherAdmin(r.To, r.Ports) for _, m := range matchers { - matcherAdmin := NewPeerMatcherANP(m, v, int(anp.Spec.Priority), r.Name) - egress.Peers = append(egress.Peers, matcherAdmin) + matcherAdmin := NewPeerMatcherANP(m, v, int(anp.Spec.Priority), anp.Name) + k := m.Pod.PrimaryKey() + m.Namespace.PrimaryKey() + m.Port.GetPrimaryKey() + egress.Peers[k] = append(egress.Peers[k], matcherAdmin) } } } - return ingress, egress } @@ -261,6 +283,7 @@ func BuildTargetBANP(banp *v1alpha1.BaselineAdminNetworkPolicy) (*Target, *Targe ingress = &Target{ SubjectMatcher: NewSubjectAdmin(&banp.Spec.Subject), SourceRules: []NetPolID{netPolID(banp)}, + Peers: make(map[string][]PeerMatcher), } for _, r := range banp.Spec.Ingress { @@ -268,7 +291,8 @@ func BuildTargetBANP(banp *v1alpha1.BaselineAdminNetworkPolicy) (*Target, *Targe matchers := BuildPeerMatcherAdmin(r.From, r.Ports) for _, m := range matchers { matcherAdmin := NewPeerMatcherBANP(m, v, r.Name) - ingress.Peers = append(ingress.Peers, matcherAdmin) + k := m.Pod.PrimaryKey() + m.Namespace.PrimaryKey() + m.Port.GetPrimaryKey() + ingress.Peers[k] = append(ingress.Peers[k], matcherAdmin) } } } @@ -277,6 +301,7 @@ func BuildTargetBANP(banp *v1alpha1.BaselineAdminNetworkPolicy) (*Target, *Targe egress = &Target{ SubjectMatcher: NewSubjectAdmin(&banp.Spec.Subject), SourceRules: []NetPolID{netPolID(banp)}, + Peers: make(map[string][]PeerMatcher), } for _, r := range banp.Spec.Egress { @@ -284,7 +309,8 @@ func BuildTargetBANP(banp *v1alpha1.BaselineAdminNetworkPolicy) (*Target, *Targe matchers := BuildPeerMatcherAdmin(r.To, r.Ports) for _, m := range matchers { matcherAdmin := NewPeerMatcherBANP(m, v, r.Name) - egress.Peers = append(egress.Peers, matcherAdmin) + k := m.Pod.PrimaryKey() + m.Namespace.PrimaryKey() + m.Port.GetPrimaryKey() + egress.Peers[k] = append(egress.Peers[k], matcherAdmin) } } } diff --git a/cmd/policy-assistant/pkg/matcher/builder_tests.go b/cmd/policy-assistant/pkg/matcher/builder_tests.go index 1f964b98..79035143 100644 --- a/cmd/policy-assistant/pkg/matcher/builder_tests.go +++ b/cmd/policy-assistant/pkg/matcher/builder_tests.go @@ -1,9 +1,12 @@ package matcher import ( + "github.com/mattfenwick/collections/pkg/slice" + "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" @@ -26,7 +29,7 @@ func RunBuilderTests() { ingress, egress := BuildTarget(netpol.AllowNoIngress) Expect(ingress).ToNot(BeNil()) - Expect(ingress.Peers).To(BeNil()) + Expect(ingress.Peers).To(BeEmpty()) Expect(egress).To(BeNil()) }) @@ -35,7 +38,7 @@ func RunBuilderTests() { ingress, egress := BuildTarget(netpol.AllowNoEgress) Expect(egress).ToNot(BeNil()) - Expect(egress.Peers).To(BeNil()) + Expect(egress.Peers).To(BeEmpty()) Expect(ingress).To(BeNil()) }) @@ -44,10 +47,10 @@ func RunBuilderTests() { ingress, egress := BuildTarget(netpol.AllowNoIngressAllowNoEgress) Expect(egress).ToNot(BeNil()) - Expect(egress.Peers).To(BeNil()) + Expect(egress.Peers).To(BeEmpty()) Expect(ingress).ToNot(BeNil()) - Expect(ingress.Peers).To(BeNil()) + Expect(ingress.Peers).To(BeEmpty()) }) }) @@ -73,7 +76,7 @@ func RunBuilderTests() { ingress, egress := BuildTarget(netpol.AllowNoIngress_EmptyIngress) Expect(ingress).ToNot(BeNil()) - Expect(ingress.Peers).To(BeNil()) + Expect(ingress.Peers).To(BeEmpty()) Expect(egress).To(BeNil()) }) @@ -82,7 +85,7 @@ func RunBuilderTests() { ingress, egress := BuildTarget(netpol.AllowNoEgress_EmptyEgress) Expect(egress).ToNot(BeNil()) - Expect(egress.Peers).To(BeNil()) + Expect(egress.Peers).To(BeEmpty()) Expect(ingress).To(BeNil()) }) @@ -91,10 +94,10 @@ func RunBuilderTests() { ingress, egress := BuildTarget(netpol.AllowNoIngressAllowNoEgress_EmptyEgressEmptyIngress) Expect(egress).ToNot(BeNil()) - Expect(egress.Peers).To(BeNil()) + Expect(egress.Peers).To(BeEmpty()) Expect(ingress).ToNot(BeNil()) - Expect(ingress.Peers).To(BeNil()) + Expect(ingress.Peers).To(BeEmpty()) }) }) @@ -103,21 +106,25 @@ func RunBuilderTests() { ingress, egress := BuildTarget(netpol.AllowAllIngress) Expect(egress).To(BeNil()) - Expect(ingress.Peers).To(Equal([]PeerMatcher{AllPeersPorts})) + Expect(ingress.Peers).To(Equal(map[string][]PeerMatcher{ + "": {AllPeersPorts}, + })) }) It("allow-all-egress", func() { ingress, egress := BuildTarget(netpol.AllowAllEgress) - Expect(egress.Peers).To(Equal([]PeerMatcher{AllPeersPorts})) + Expect(egress.Peers).To(Equal(map[string][]PeerMatcher{ + "": {AllPeersPorts}, + })) Expect(ingress).To(BeNil()) }) It("allow-all-both", func() { ingress, egress := BuildTarget(netpol.AllowAllIngressAllowAllEgress) - Expect(egress.Peers).To(Equal([]PeerMatcher{AllPeersPorts})) - Expect(ingress.Peers).To(Equal([]PeerMatcher{AllPeersPorts})) + Expect(egress.Peers).To(Equal(map[string][]PeerMatcher{"": {AllPeersPorts}})) + Expect(ingress.Peers).To(Equal(map[string][]PeerMatcher{"": {AllPeersPorts}})) }) }) @@ -339,4 +346,55 @@ func RunBuilderTests() { }}})) }) }) + + Describe("BuildV1AndV2NetPols", func() { + It("it combines ANPs with same subject", func() { + result := BuildV1AndV2NetPols(false, 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)) + + }) + + It("it combines ANPs with same peers and protocol targets", func() { + result := BuildV1AndV2NetPols(false, nil, examples.SimpleANPs, nil) + k := maps.Keys(result.Egress) + firstRule := result.Egress[k[0]] + pk := maps.Keys(firstRule.Peers) + peers := firstRule.Peers[pk[0]] + Expect(peers).To(HaveLen(2)) + + }) + + It("it adds BANPs rules to ANPs if peers and protocol match", func() { + result := BuildV1AndV2NetPols(false, nil, examples.SimpleANPs, examples.SimpleBANP) + k := maps.Keys(result.Egress) + firstRule := result.Egress[k[0]] + pk := maps.Keys(firstRule.Peers) + peers := firstRule.Peers[pk[0]] + anps := slice.Filter(func(a PeerMatcher) bool { + switch t := a.(type) { + case *PeerMatcherAdmin: + return t.effectFromMatch.PolicyKind == AdminNetworkPolicy + default: + return false + } + }, peers) + + Expect(anps).To(HaveLen(2)) + + banps := slice.Filter(func(a PeerMatcher) bool { + switch t := a.(type) { + case *PeerMatcherAdmin: + return t.effectFromMatch.PolicyKind == BaselineAdminNetworkPolicy + default: + return false + } + }, peers) + + Expect(banps).To(HaveLen(1)) + + }) + }) } diff --git a/cmd/policy-assistant/pkg/matcher/explain.go b/cmd/policy-assistant/pkg/matcher/explain.go index ba6880fc..c95c2619 100644 --- a/cmd/policy-assistant/pkg/matcher/explain.go +++ b/cmd/policy-assistant/pkg/matcher/explain.go @@ -2,10 +2,10 @@ package matcher import ( "fmt" - "github.com/mattfenwick/collections/pkg/json" + "golang.org/x/exp/maps" + "golang.org/x/exp/slices" "strings" - "github.com/mattfenwick/collections/pkg/slice" "github.com/mattfenwick/cyclonus/pkg/kube" "github.com/olekukonko/tablewriter" "github.com/pkg/errors" @@ -55,33 +55,112 @@ func (s *SliceBuilder) TargetsTableLines(targets []*Target, isIngress bool) { 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 actions", "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, a.Name) + for _, peers := range target.Peers { + if len(peers) == 0 { + s.Append("no pods, no ips", "NPv1: All peers allowed", "no ports, no protocols") + continue + } + var subject string + var ports string + var action string + + anps := []*PeerMatcherAdmin{} + banps := []*PeerMatcherAdmin{} + + for _, p := range 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(a.Port, NetworkPolicyV1) + pps := PortMatcherTableLines(t.Port, NetworkPolicyV1) s.Append("all pods, all ips", "", strings.Join(pps, "\n")) case *IPPeerMatcher: - s.IPPeerMatcherTableLines(a) + s.IPPeerMatcherTableLines(t) case *PodPeerMatcher: - s.PodPeerMatcherTableLines(a, NewV1Effect(true), "") - default: - continue + s.PodPeerMatcherTableLines(t, NewV1Effect(true), "") + case *PeerMatcherAdmin: + subject = resolveSubject(t.PodPeerMatcher) + ports = strings.Join(PortMatcherTableLines(t.PodPeerMatcher.Port, t.effectFromMatch.PolicyKind), "\n") + + switch t.effectFromMatch.PolicyKind { + case AdminNetworkPolicy: + anps = append(anps, t) + case BaselineAdminNetworkPolicy: + banps = append(banps, t) + default: + panic("This should not be possible") + } + } + } + + if len(anps) > 1 { + g := map[string]*AnpGroup{} + for _, v := range anps { + e := string(v.effectFromMatch.Verdict) + if _, ok := g[v.Name]; !ok { + g[v.Name] = &AnpGroup{ + name: v.Name, + priority: v.effectFromMatch.Priority, + effects: []string{}, + } + } + g[v.Name].effects = append(g[v.Name].effects, e) + } + + groups := maps.Values(g) + + slices.SortFunc(groups, func(a, b *AnpGroup) bool { + return a.priority > b.priority + }) + + r := []string{"ANP:"} + for _, a := range groups { + if len(a.effects[1:]) > 0 { + r = append(r, fmt.Sprintf(" pri=%d (%s): %s (ineffective rules: %s)", a.priority, a.name, a.effects[0], strings.Join(a.effects[1:], ", "))) + } else { + r = append(r, fmt.Sprintf(" pri=%d (%s): %s", a.priority, a.name, a.effects[0])) + } } + action = strings.Join(r, "\n") + "\n" } + + if len(banps) >= 1 { + g := map[string]*AnpGroup{} + for _, v := range banps { + e := string(v.effectFromMatch.Verdict) + if _, ok := g[v.Name]; !ok { + g[v.Name] = &AnpGroup{ + name: v.Name, + priority: v.effectFromMatch.Priority, + effects: []string{}, + } + } + g[v.Name].effects = append(g[v.Name].effects, e) + } + for _, a := range g { + if len(a.effects[1:]) > 0 { + action += fmt.Sprintf("BNP: %s (ineffective rules: %s)", a.priority, a.name, a.effects[0], strings.Join(a.effects[1:], ", ")) + } else { + action += fmt.Sprintf("BNP: %s", a.effects[0]) + } + } + } + s.Append(subject, action, ports) } + } } +type AnpGroup struct { + name string + priority int + effects []string +} + func (s *SliceBuilder) IPPeerMatcherTableLines(ip *IPPeerMatcher) { peer := ip.IPBlock.CIDR + "\n" + fmt.Sprintf("except %+v", ip.IPBlock.Except) pps := PortMatcherTableLines(ip.Port, NetworkPolicyV1) @@ -89,31 +168,7 @@ func (s *SliceBuilder) IPPeerMatcherTableLines(ip *IPPeerMatcher) { } func (s *SliceBuilder) PodPeerMatcherTableLines(nsPodMatcher *PodPeerMatcher, e Effect, name string) { - var namespaces 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)) - } - 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)) - } - s.Append(fmt.Sprintf("Namespace:\n %s\nPod:\n %s", strings.TrimSpace(namespaces), strings.TrimSpace(pods)), priorityTableLine(e, name), strings.Join(PortMatcherTableLines(nsPodMatcher.Port, e.PolicyKind), "\n")) + s.Append(resolveSubject(nsPodMatcher), priorityTableLine(e, name), strings.Join(PortMatcherTableLines(nsPodMatcher.Port, e.PolicyKind), "\n")) } func PortMatcherTableLines(pm PortMatcher, kind PolicyKind) []string { @@ -148,11 +203,40 @@ func priorityTableLine(e Effect, name string) string { if e.PolicyKind == NetworkPolicyV1 { return "NPv1: All peers allowed" } else if e.PolicyKind == AdminNetworkPolicy { - return fmt.Sprintf("%s (%s): %s (pri=%d)", e.PolicyKind, name, e.Verdict, e.Priority) + return fmt.Sprintf(" (pri=%d) %s: %s ", e.Priority, name, e.Verdict) } else if e.PolicyKind == BaselineAdminNetworkPolicy { - return fmt.Sprintf("%s (%s): %s", e.PolicyKind, name, e.Verdict) + return fmt.Sprintf(" (%s): %s", name, e.Verdict) } else { panic(errors.Errorf("Invalid effect %s", e.PolicyKind)) } +} + +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/policy.go b/cmd/policy-assistant/pkg/matcher/policy.go index 1aa3fc8d..1a1737d7 100644 --- a/cmd/policy-assistant/pkg/matcher/policy.go +++ b/cmd/policy-assistant/pkg/matcher/policy.go @@ -300,19 +300,23 @@ func (p *Policy) IsIngressOrEgressAllowed(traffic *Traffic, isIngress bool) Dire // 3. Check if any matching targets allow this traffic effects := make([]Effect, 0) for _, target := range matchingTargets { - for _, m := range target.Peers { - // check if m is a PeerMatcherAdmin - e := NewV1Effect(true) - matcherAdmin, ok := m.(*PeerMatcherAdmin) - if ok { - e = matcherAdmin.effectFromMatch + for _, g := range target.Peers { + for _, m := range g { + _ = m + _ = peer + // check if m is a PeerMatcherAdmin + e := NewV1Effect(true) + matcherAdmin, ok := m.(*PeerMatcherAdmin) + if ok { + e = matcherAdmin.effectFromMatch + } + + if !m.Matches(subject, peer, traffic.ResolvedPort, traffic.ResolvedPortName, traffic.Protocol) { + e.Verdict = None + } + + effects = append(effects, e) } - - if !m.Matches(subject, peer, traffic.ResolvedPort, traffic.ResolvedPortName, traffic.Protocol) { - e.Verdict = None - } - - effects = append(effects, e) } } 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 43fd65a8..e124cf3b 100644 --- a/cmd/policy-assistant/pkg/matcher/target.go +++ b/cmd/policy-assistant/pkg/matcher/target.go @@ -2,11 +2,12 @@ package matcher import ( "fmt" - + "github.com/mattfenwick/collections/pkg/slice" "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" ) @@ -48,7 +49,7 @@ type Target struct { // Peers contains all matchers for a Target. // Order matters for rules in the same ANP or BANP. // Priority matters for rules in different ANPs. - Peers []PeerMatcher + Peers map[string][]PeerMatcher } func (t *Target) String() string { @@ -56,7 +57,7 @@ func (t *Target) String() string { } func (t *Target) Simplify() { - t.Peers = Simplify(t.Peers) + //t.Peers = Simplify(t.Peers) } // Combine creates a new Target combining the egress and ingress rules @@ -69,13 +70,65 @@ func (t *Target) Combine(other *Target) *Target { panic(errors.Errorf("cannot combine targets: primary keys differ -- '%s' vs '%s'", myPk, otherPk)) } + peers := map[string][]PeerMatcher{} + + peers = combinePeers(peers, t.Peers) + peers = combinePeers(peers, other.Peers) + + // ensure that we have only one banp after combining + for k := range peers { + var banp bool + peers[k] = slice.Filter(func(a PeerMatcher) bool { + switch t := a.(type) { + case *PeerMatcherAdmin: + if t.effectFromMatch.PolicyKind == BaselineAdminNetworkPolicy { + if banp { + return false + } + banp = true + return true + + } + return true + default: + return true + } + }, peers[k]) + + } + return &Target{ SubjectMatcher: t.SubjectMatcher, - Peers: append(t.Peers, other.Peers...), - SourceRules: append(t.SourceRules, other.SourceRules...), + Peers: peers, + SourceRules: sets.New(t.SourceRules...).Insert(other.SourceRules...).UnsortedList(), } } +func (t *Target) CombineCommonPeers(other *Target) { + if other == nil || len(other.Peers) == 0 { + return + } + + rules := sets.New(t.SourceRules...) + for k := range t.Peers { + if _, ok := other.Peers[k]; ok { + t.Peers[k] = append(t.Peers[k], other.Peers[k]...) + rules.Insert(other.SourceRules...) + } + } + t.SourceRules = rules.UnsortedList() +} + +func combinePeers(dest map[string][]PeerMatcher, source map[string][]PeerMatcher) map[string][]PeerMatcher { + for i, v := range source { + if _, ok := dest[i]; !ok { + dest[i] = []PeerMatcher{} + } + dest[i] = append(dest[i], v...) + } + return dest +} + // CombineTargetsIgnoringPrimaryKey creates a new v1 target from the given namespace and pod selector, // and combines all the edges and source rules from the original targets into the new target. func CombineTargetsIgnoringPrimaryKey(namespace string, podSelector metav1.LabelSelector, targets []*Target) *Target { @@ -87,10 +140,10 @@ func CombineTargetsIgnoringPrimaryKey(namespace string, podSelector metav1.Label Peers: targets[0].Peers, SourceRules: targets[0].SourceRules, } - for _, t := range targets[1:] { - target.Peers = append(target.Peers, t.Peers...) - target.SourceRules = append(target.SourceRules, t.SourceRules...) - } + //for _, t := range targets[1:] { + // target.Peers = append(target.Peers, t.Peers...) + // target.SourceRules = append(target.SourceRules, t.SourceRules...) + //} return target } From ec0c4a42aea3da035682ca5f9feae8e9da750f72 Mon Sep 17 00:00:00 2001 From: Nikola Date: Tue, 27 Feb 2024 21:36:54 +0200 Subject: [PATCH 3/4] refactor explain command that handle anp and banp --- cmd/policy-assistant/examples/example.go | 44 +--- .../pkg/kube/labelselector.go | 4 +- cmd/policy-assistant/pkg/matcher/builder.go | 60 ++--- .../pkg/matcher/builder_tests.go | 72 +----- cmd/policy-assistant/pkg/matcher/explain.go | 232 ++++++++++-------- cmd/policy-assistant/pkg/matcher/policy.go | 28 +-- cmd/policy-assistant/pkg/matcher/target.go | 77 ++---- .../test/integration/explain_test.go | 23 ++ 8 files changed, 209 insertions(+), 331 deletions(-) create mode 100644 cmd/policy-assistant/test/integration/explain_test.go diff --git a/cmd/policy-assistant/examples/example.go b/cmd/policy-assistant/examples/example.go index 34100450..5b37d252 100644 --- a/cmd/policy-assistant/examples/example.go +++ b/cmd/policy-assistant/examples/example.go @@ -9,7 +9,7 @@ import ( var CoreGressRulesCombinedANB = []*v1alpha1.AdminNetworkPolicy{ { ObjectMeta: v1.ObjectMeta{ - Name: "Example ANP", + Name: "example-anp", }, Spec: v1alpha1.AdminNetworkPolicySpec{ Priority: 20, @@ -19,7 +19,6 @@ var CoreGressRulesCombinedANB = []*v1alpha1.AdminNetworkPolicy{ { Key: "kubernetes.io/metadata.name", Operator: v1.LabelSelectorOpExists, - Values: []string{"network-policy-conformance-gryffindor"}, }, }, }, @@ -308,7 +307,7 @@ var CoreGressRulesCombinedANB = []*v1alpha1.AdminNetworkPolicy{ }, { ObjectMeta: v1.ObjectMeta{ - Name: "Example ANP 2", + Name: "example-anp-2", }, Spec: v1alpha1.AdminNetworkPolicySpec{ Priority: 16, @@ -318,7 +317,6 @@ var CoreGressRulesCombinedANB = []*v1alpha1.AdminNetworkPolicy{ { Key: "kubernetes.io/metadata.name", Operator: v1.LabelSelectorOpExists, - Values: []string{"network-policy-conformance-gryffindor"}, }, }, }, @@ -618,7 +616,6 @@ var CoreGressRulesCombinedBANB *v1alpha1.BaselineAdminNetworkPolicy = &v1alpha1. { Key: "kubernetes.io/metadata.name", Operator: v1.LabelSelectorOpExists, - Values: []string{"network-policy-conformance-gryffindor"}, }, }, }, @@ -657,7 +654,6 @@ var CoreGressRulesCombinedBANB *v1alpha1.BaselineAdminNetworkPolicy = &v1alpha1. { Key: "kubernetes.io/metadata.name", Operator: v1.LabelSelectorOpExists, - Values: []string{"network-policy-conformance-gryffindor"}, }, }, }, @@ -808,7 +804,7 @@ var CoreGressRulesCombinedBANB *v1alpha1.BaselineAdminNetworkPolicy = &v1alpha1. var SimpleANPs = []*v1alpha1.AdminNetworkPolicy{ { ObjectMeta: v1.ObjectMeta{ - Name: "Simple ANP 1", + Name: "simple-anp-1", }, Spec: v1alpha1.AdminNetworkPolicySpec{ Priority: 34, @@ -840,7 +836,7 @@ var SimpleANPs = []*v1alpha1.AdminNetworkPolicy{ }, { ObjectMeta: v1.ObjectMeta{ - Name: "Simple ANP 2", + Name: "simple-anp-2", }, Spec: v1alpha1.AdminNetworkPolicySpec{ Priority: 50, @@ -871,35 +867,3 @@ var SimpleANPs = []*v1alpha1.AdminNetworkPolicy{ }, }, } - -var SimpleBANP *v1alpha1.BaselineAdminNetworkPolicy = &v1alpha1.BaselineAdminNetworkPolicy{ - ObjectMeta: v1.ObjectMeta{ - Name: "Simple BANP", - }, - Spec: v1alpha1.BaselineAdminNetworkPolicySpec{ - Subject: v1alpha1.AdminNetworkPolicySubject{ - Namespaces: &v1.LabelSelector{ - MatchLabels: map[string]string{ - "test": "test", - }, - }, - }, - Egress: []v1alpha1.BaselineAdminNetworkPolicyEgressRule{ - { - Name: "allow-to-ravenclaw-everything", - Action: v1alpha1.BaselineAdminNetworkPolicyRuleActionAllow, - 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/kube/labelselector.go b/cmd/policy-assistant/pkg/kube/labelselector.go index e378965c..699fef6e 100644 --- a/cmd/policy-assistant/pkg/kube/labelselector.go +++ b/cmd/policy-assistant/pkg/kube/labelselector.go @@ -109,7 +109,7 @@ func LabelSelectorTableLines(selector metav1.LabelSelector) string { if len(selector.MatchLabels) > 0 { 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 { @@ -117,7 +117,7 @@ func LabelSelectorTableLines(selector metav1.LabelSelector) string { 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 f44a1f59..d3b8919c 100644 --- a/cmd/policy-assistant/pkg/matcher/builder.go +++ b/cmd/policy-assistant/pkg/matcher/builder.go @@ -21,30 +21,21 @@ func BuildV1AndV2NetPols(simplify bool, netpols []*networkingv1.NetworkPolicy, a np.AddTarget(false, egress) } - var banpIngress *Target - var banpEgress *Target - - if banp != nil { - // there can only be one BANP by definition - banpIngress, banpEgress = BuildTargetBANP(banp) - np.AddTarget(true, banpIngress) - np.AddTarget(false, banpEgress) - } - priorities := make(map[int32]struct{}) for _, p := range anps { if _, ok := priorities[p.Spec.Priority]; ok { - panic(errors.Errorf("duplicate priorities are now allowed. priority: %d", p.Spec.Priority)) + panic(errors.Errorf("duplicate priorities are undefined. priority: %d", p.Spec.Priority)) } priorities[p.Spec.Priority] = struct{}{} ingress, egress := BuildTargetANP(p) - if banpIngress != nil && ingress.GetPrimaryKey() == banpIngress.GetPrimaryKey() { - ingress.CombineCommonPeers(banpIngress) - egress.CombineCommonPeers(banpEgress) - - } + np.AddTarget(true, ingress) + np.AddTarget(false, egress) + } + if banp != nil { + // there can only be one BANP by definition + ingress, egress := BuildTargetBANP(banp) np.AddTarget(true, ingress) np.AddTarget(false, egress) } @@ -73,27 +64,16 @@ func BuildTarget(netpol *networkingv1.NetworkPolicy) (*Target, *Target) { for _, pType := range netpol.Spec.PolicyTypes { switch pType { case networkingv1.PolicyTypeIngress: - p := map[string][]PeerMatcher{} - ingressPeers := BuildIngressMatcher(policyNamespace, netpol.Spec.Ingress) - if len(ingressPeers) > 0 { - p[""] = ingressPeers - } - ingress = &Target{ SubjectMatcher: NewSubjectV1(policyNamespace, netpol.Spec.PodSelector), SourceRules: []NetPolID{netPolID(netpol)}, - Peers: p, + Peers: BuildIngressMatcher(policyNamespace, netpol.Spec.Ingress), } case networkingv1.PolicyTypeEgress: - p := map[string][]PeerMatcher{} - egressPeers := BuildEgressMatcher(policyNamespace, netpol.Spec.Egress) - if len(egressPeers) > 0 { - p[""] = egressPeers - } egress = &Target{ SubjectMatcher: NewSubjectV1(policyNamespace, netpol.Spec.PodSelector), SourceRules: []NetPolID{netPolID(netpol)}, - Peers: p, + Peers: BuildEgressMatcher(policyNamespace, netpol.Spec.Egress), } } } @@ -238,7 +218,6 @@ func BuildTargetANP(anp *v1alpha1.AdminNetworkPolicy) (*Target, *Target) { ingress = &Target{ SubjectMatcher: NewSubjectAdmin(&anp.Spec.Subject), SourceRules: []NetPolID{netPolID(anp)}, - Peers: make(map[string][]PeerMatcher), } for _, r := range anp.Spec.Ingress { @@ -246,16 +225,15 @@ func BuildTargetANP(anp *v1alpha1.AdminNetworkPolicy) (*Target, *Target) { matchers := BuildPeerMatcherAdmin(r.From, r.Ports) for _, m := range matchers { matcherAdmin := NewPeerMatcherANP(m, v, int(anp.Spec.Priority), anp.Name) - k := m.Pod.PrimaryKey() + m.Namespace.PrimaryKey() + m.Port.GetPrimaryKey() - ingress.Peers[k] = append(ingress.Peers[k], matcherAdmin) + ingress.Peers = append(ingress.Peers, matcherAdmin) } } } + if len(anp.Spec.Egress) > 0 { egress = &Target{ SubjectMatcher: NewSubjectAdmin(&anp.Spec.Subject), SourceRules: []NetPolID{netPolID(anp)}, - Peers: make(map[string][]PeerMatcher), } for _, r := range anp.Spec.Egress { @@ -263,11 +241,11 @@ func BuildTargetANP(anp *v1alpha1.AdminNetworkPolicy) (*Target, *Target) { matchers := BuildPeerMatcherAdmin(r.To, r.Ports) for _, m := range matchers { matcherAdmin := NewPeerMatcherANP(m, v, int(anp.Spec.Priority), anp.Name) - k := m.Pod.PrimaryKey() + m.Namespace.PrimaryKey() + m.Port.GetPrimaryKey() - egress.Peers[k] = append(egress.Peers[k], matcherAdmin) + egress.Peers = append(egress.Peers, matcherAdmin) } } } + return ingress, egress } @@ -283,16 +261,14 @@ func BuildTargetBANP(banp *v1alpha1.BaselineAdminNetworkPolicy) (*Target, *Targe ingress = &Target{ SubjectMatcher: NewSubjectAdmin(&banp.Spec.Subject), SourceRules: []NetPolID{netPolID(banp)}, - Peers: make(map[string][]PeerMatcher), } for _, r := range banp.Spec.Ingress { v := BaselineAdminActionToVerdict(r.Action) matchers := BuildPeerMatcherAdmin(r.From, r.Ports) for _, m := range matchers { - matcherAdmin := NewPeerMatcherBANP(m, v, r.Name) - k := m.Pod.PrimaryKey() + m.Namespace.PrimaryKey() + m.Port.GetPrimaryKey() - ingress.Peers[k] = append(ingress.Peers[k], matcherAdmin) + matcherAdmin := NewPeerMatcherBANP(m, v, banp.Name) + ingress.Peers = append(ingress.Peers, matcherAdmin) } } } @@ -301,16 +277,14 @@ func BuildTargetBANP(banp *v1alpha1.BaselineAdminNetworkPolicy) (*Target, *Targe egress = &Target{ SubjectMatcher: NewSubjectAdmin(&banp.Spec.Subject), SourceRules: []NetPolID{netPolID(banp)}, - Peers: make(map[string][]PeerMatcher), } for _, r := range banp.Spec.Egress { v := BaselineAdminActionToVerdict(r.Action) matchers := BuildPeerMatcherAdmin(r.To, r.Ports) for _, m := range matchers { - matcherAdmin := NewPeerMatcherBANP(m, v, r.Name) - k := m.Pod.PrimaryKey() + m.Namespace.PrimaryKey() + m.Port.GetPrimaryKey() - egress.Peers[k] = append(egress.Peers[k], matcherAdmin) + 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 79035143..84ccd4df 100644 --- a/cmd/policy-assistant/pkg/matcher/builder_tests.go +++ b/cmd/policy-assistant/pkg/matcher/builder_tests.go @@ -1,7 +1,6 @@ package matcher import ( - "github.com/mattfenwick/collections/pkg/slice" "github.com/mattfenwick/cyclonus/examples" "github.com/mattfenwick/cyclonus/pkg/kube/netpol" . "github.com/onsi/ginkgo/v2" @@ -29,7 +28,7 @@ func RunBuilderTests() { ingress, egress := BuildTarget(netpol.AllowNoIngress) Expect(ingress).ToNot(BeNil()) - Expect(ingress.Peers).To(BeEmpty()) + Expect(ingress.Peers).To(BeNil()) Expect(egress).To(BeNil()) }) @@ -38,7 +37,7 @@ func RunBuilderTests() { ingress, egress := BuildTarget(netpol.AllowNoEgress) Expect(egress).ToNot(BeNil()) - Expect(egress.Peers).To(BeEmpty()) + Expect(egress.Peers).To(BeNil()) Expect(ingress).To(BeNil()) }) @@ -47,10 +46,10 @@ func RunBuilderTests() { ingress, egress := BuildTarget(netpol.AllowNoIngressAllowNoEgress) Expect(egress).ToNot(BeNil()) - Expect(egress.Peers).To(BeEmpty()) + Expect(egress.Peers).To(BeNil()) Expect(ingress).ToNot(BeNil()) - Expect(ingress.Peers).To(BeEmpty()) + Expect(ingress.Peers).To(BeNil()) }) }) @@ -76,7 +75,7 @@ func RunBuilderTests() { ingress, egress := BuildTarget(netpol.AllowNoIngress_EmptyIngress) Expect(ingress).ToNot(BeNil()) - Expect(ingress.Peers).To(BeEmpty()) + Expect(ingress.Peers).To(BeNil()) Expect(egress).To(BeNil()) }) @@ -85,7 +84,7 @@ func RunBuilderTests() { ingress, egress := BuildTarget(netpol.AllowNoEgress_EmptyEgress) Expect(egress).ToNot(BeNil()) - Expect(egress.Peers).To(BeEmpty()) + Expect(egress.Peers).To(BeNil()) Expect(ingress).To(BeNil()) }) @@ -94,10 +93,10 @@ func RunBuilderTests() { ingress, egress := BuildTarget(netpol.AllowNoIngressAllowNoEgress_EmptyEgressEmptyIngress) Expect(egress).ToNot(BeNil()) - Expect(egress.Peers).To(BeEmpty()) + Expect(egress.Peers).To(BeNil()) Expect(ingress).ToNot(BeNil()) - Expect(ingress.Peers).To(BeEmpty()) + Expect(ingress.Peers).To(BeNil()) }) }) @@ -106,25 +105,21 @@ func RunBuilderTests() { ingress, egress := BuildTarget(netpol.AllowAllIngress) Expect(egress).To(BeNil()) - Expect(ingress.Peers).To(Equal(map[string][]PeerMatcher{ - "": {AllPeersPorts}, - })) + Expect(ingress.Peers).To(Equal([]PeerMatcher{AllPeersPorts})) }) It("allow-all-egress", func() { ingress, egress := BuildTarget(netpol.AllowAllEgress) - Expect(egress.Peers).To(Equal(map[string][]PeerMatcher{ - "": {AllPeersPorts}, - })) + Expect(egress.Peers).To(Equal([]PeerMatcher{AllPeersPorts})) Expect(ingress).To(BeNil()) }) It("allow-all-both", func() { ingress, egress := BuildTarget(netpol.AllowAllIngressAllowAllEgress) - Expect(egress.Peers).To(Equal(map[string][]PeerMatcher{"": {AllPeersPorts}})) - Expect(ingress.Peers).To(Equal(map[string][]PeerMatcher{"": {AllPeersPorts}})) + Expect(egress.Peers).To(Equal([]PeerMatcher{AllPeersPorts})) + Expect(ingress.Peers).To(Equal([]PeerMatcher{AllPeersPorts})) }) }) @@ -349,52 +344,11 @@ func RunBuilderTests() { Describe("BuildV1AndV2NetPols", func() { It("it combines ANPs with same subject", func() { - result := BuildV1AndV2NetPols(false, nil, examples.SimpleANPs, nil) + 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)) - - }) - - It("it combines ANPs with same peers and protocol targets", func() { - result := BuildV1AndV2NetPols(false, nil, examples.SimpleANPs, nil) - k := maps.Keys(result.Egress) - firstRule := result.Egress[k[0]] - pk := maps.Keys(firstRule.Peers) - peers := firstRule.Peers[pk[0]] - Expect(peers).To(HaveLen(2)) - - }) - - It("it adds BANPs rules to ANPs if peers and protocol match", func() { - result := BuildV1AndV2NetPols(false, nil, examples.SimpleANPs, examples.SimpleBANP) - k := maps.Keys(result.Egress) - firstRule := result.Egress[k[0]] - pk := maps.Keys(firstRule.Peers) - peers := firstRule.Peers[pk[0]] - anps := slice.Filter(func(a PeerMatcher) bool { - switch t := a.(type) { - case *PeerMatcherAdmin: - return t.effectFromMatch.PolicyKind == AdminNetworkPolicy - default: - return false - } - }, peers) - - Expect(anps).To(HaveLen(2)) - - banps := slice.Filter(func(a PeerMatcher) bool { - switch t := a.(type) { - case *PeerMatcherAdmin: - return t.effectFromMatch.PolicyKind == BaselineAdminNetworkPolicy - default: - return false - } - }, peers) - - Expect(banps).To(HaveLen(1)) - }) }) } diff --git a/cmd/policy-assistant/pkg/matcher/explain.go b/cmd/policy-assistant/pkg/matcher/explain.go index c95c2619..8a44d11b 100644 --- a/cmd/policy-assistant/pkg/matcher/explain.go +++ b/cmd/policy-assistant/pkg/matcher/explain.go @@ -2,8 +2,10 @@ package matcher import ( "fmt" - "golang.org/x/exp/maps" + "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" @@ -11,6 +13,23 @@ import ( "github.com/pkg/errors" ) +type peerProtocolGroup struct { + port string + subject string + policies map[string]*anpGroup +} + +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 @@ -59,116 +78,80 @@ func (s *SliceBuilder) TargetsTableLines(targets []*Target, isIngress bool) { rules := strings.Join(sourceRulesStrings, "\n") s.Prefix = []string{ruleType, target.TargetString(), rules} - for _, peers := range target.Peers { - if len(peers) == 0 { - s.Append("no pods, no ips", "NPv1: All peers allowed", "no ports, no protocols") - continue - } - var subject string - var ports string - var action string - - anps := []*PeerMatcherAdmin{} - banps := []*PeerMatcherAdmin{} - - for _, p := range 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", "", strings.Join(pps, "\n")) - case *IPPeerMatcher: - s.IPPeerMatcherTableLines(t) - case *PodPeerMatcher: - s.PodPeerMatcherTableLines(t, NewV1Effect(true), "") - case *PeerMatcherAdmin: - subject = resolveSubject(t.PodPeerMatcher) - ports = strings.Join(PortMatcherTableLines(t.PodPeerMatcher.Port, t.effectFromMatch.PolicyKind), "\n") - - switch t.effectFromMatch.PolicyKind { - case AdminNetworkPolicy: - anps = append(anps, t) - case BaselineAdminNetworkPolicy: - banps = append(banps, t) - default: - panic("This should not be possible") - } - } - } - - if len(anps) > 1 { - g := map[string]*AnpGroup{} - for _, v := range anps { - e := string(v.effectFromMatch.Verdict) - if _, ok := g[v.Name]; !ok { - g[v.Name] = &AnpGroup{ - name: v.Name, - priority: v.effectFromMatch.Priority, - effects: []string{}, - } - } - g[v.Name].effects = append(g[v.Name].effects, e) - } - - groups := maps.Values(g) - - slices.SortFunc(groups, func(a, b *AnpGroup) bool { - return a.priority > b.priority - }) + if len(target.Peers) == 0 { + s.Append("no pods, no ips", "NPv1: All peers allowed", "no ports, no protocols") + continue + } - r := []string{"ANP:"} - for _, a := range groups { - if len(a.effects[1:]) > 0 { - r = append(r, fmt.Sprintf(" pri=%d (%s): %s (ineffective rules: %s)", a.priority, a.name, a.effects[0], strings.Join(a.effects[1:], ", "))) - } else { - r = append(r, fmt.Sprintf(" pri=%d (%s): %s", a.priority, a.name, a.effects[0])) - } - } - action = strings.Join(r, "\n") + "\n" + 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)) } - - if len(banps) >= 1 { - g := map[string]*AnpGroup{} - for _, v := range banps { - e := string(v.effectFromMatch.Verdict) - if _, ok := g[v.Name]; !ok { - g[v.Name] = &AnpGroup{ - name: v.Name, - priority: v.effectFromMatch.Priority, - effects: []string{}, - } - } - g[v.Name].effects = append(g[v.Name].effects, e) - } - for _, a := range g { - if len(a.effects[1:]) > 0 { - action += fmt.Sprintf("BNP: %s (ineffective rules: %s)", a.priority, a.name, a.effects[0], strings.Join(a.effects[1:], ", ")) - } else { - action += fmt.Sprintf("BNP: %s", a.effects[0]) - } - } - } - s.Append(subject, action, ports) } } } -type AnpGroup struct { - name string - priority int - effects []string -} - 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, name string) { - s.Append(resolveSubject(nsPodMatcher), priorityTableLine(e, name), strings.Join(PortMatcherTableLines(nsPodMatcher.Port, e.PolicyKind), "\n")) +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) + } + } + 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): %s (ineffective rules: %s)", v.name, v.effects[0], strings.Join(v.effects[1:], ", "))) + } else { + actions = append(actions, fmt.Sprintf(" (%s): %s", v.name, v.effects[0])) + } + } + } + + s.Append(t.subject, strings.Join(actions, "\n"), t.port) } func PortMatcherTableLines(pm PortMatcher, kind PolicyKind) []string { @@ -199,16 +182,49 @@ func PortMatcherTableLines(pm PortMatcher, kind PolicyKind) []string { } } -func priorityTableLine(e Effect, name string) string { - if e.PolicyKind == NetworkPolicyV1 { - return "NPv1: All peers allowed" - } else if e.PolicyKind == AdminNetworkPolicy { - return fmt.Sprintf(" (pri=%d) %s: %s ", e.Priority, name, e.Verdict) - } else if e.PolicyKind == BaselineAdminNetworkPolicy { - return fmt.Sprintf(" (%s): %s", name, e.Verdict) - } else { - panic(errors.Errorf("Invalid effect %s", e.PolicyKind)) +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() + 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 { + return a.port < b.port + }) + + for _, v := range groupResult { + result = append(result, v) + } + + return result } func resolveSubject(nsPodMatcher *PodPeerMatcher) string { diff --git a/cmd/policy-assistant/pkg/matcher/policy.go b/cmd/policy-assistant/pkg/matcher/policy.go index 1a1737d7..1aa3fc8d 100644 --- a/cmd/policy-assistant/pkg/matcher/policy.go +++ b/cmd/policy-assistant/pkg/matcher/policy.go @@ -300,23 +300,19 @@ func (p *Policy) IsIngressOrEgressAllowed(traffic *Traffic, isIngress bool) Dire // 3. Check if any matching targets allow this traffic effects := make([]Effect, 0) for _, target := range matchingTargets { - for _, g := range target.Peers { - for _, m := range g { - _ = m - _ = peer - // check if m is a PeerMatcherAdmin - e := NewV1Effect(true) - matcherAdmin, ok := m.(*PeerMatcherAdmin) - if ok { - e = matcherAdmin.effectFromMatch - } - - if !m.Matches(subject, peer, traffic.ResolvedPort, traffic.ResolvedPortName, traffic.Protocol) { - e.Verdict = None - } - - effects = append(effects, e) + for _, m := range target.Peers { + // check if m is a PeerMatcherAdmin + e := NewV1Effect(true) + matcherAdmin, ok := m.(*PeerMatcherAdmin) + if ok { + e = matcherAdmin.effectFromMatch } + + if !m.Matches(subject, peer, traffic.ResolvedPort, traffic.ResolvedPortName, traffic.Protocol) { + e.Verdict = None + } + + effects = append(effects, e) } } diff --git a/cmd/policy-assistant/pkg/matcher/target.go b/cmd/policy-assistant/pkg/matcher/target.go index e124cf3b..a3228884 100644 --- a/cmd/policy-assistant/pkg/matcher/target.go +++ b/cmd/policy-assistant/pkg/matcher/target.go @@ -2,13 +2,13 @@ package matcher import ( "fmt" - "github.com/mattfenwick/collections/pkg/slice" "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" @@ -49,7 +49,7 @@ type Target struct { // Peers contains all matchers for a Target. // Order matters for rules in the same ANP or BANP. // Priority matters for rules in different ANPs. - Peers map[string][]PeerMatcher + Peers []PeerMatcher } func (t *Target) String() string { @@ -57,7 +57,7 @@ func (t *Target) String() string { } func (t *Target) Simplify() { - //t.Peers = Simplify(t.Peers) + t.Peers = Simplify(t.Peers) } // Combine creates a new Target combining the egress and ingress rules @@ -70,65 +70,13 @@ func (t *Target) Combine(other *Target) *Target { panic(errors.Errorf("cannot combine targets: primary keys differ -- '%s' vs '%s'", myPk, otherPk)) } - peers := map[string][]PeerMatcher{} - - peers = combinePeers(peers, t.Peers) - peers = combinePeers(peers, other.Peers) - - // ensure that we have only one banp after combining - for k := range peers { - var banp bool - peers[k] = slice.Filter(func(a PeerMatcher) bool { - switch t := a.(type) { - case *PeerMatcherAdmin: - if t.effectFromMatch.PolicyKind == BaselineAdminNetworkPolicy { - if banp { - return false - } - banp = true - return true - - } - return true - default: - return true - } - }, peers[k]) - - } - return &Target{ SubjectMatcher: t.SubjectMatcher, - Peers: peers, + Peers: append(t.Peers, other.Peers...), SourceRules: sets.New(t.SourceRules...).Insert(other.SourceRules...).UnsortedList(), } } -func (t *Target) CombineCommonPeers(other *Target) { - if other == nil || len(other.Peers) == 0 { - return - } - - rules := sets.New(t.SourceRules...) - for k := range t.Peers { - if _, ok := other.Peers[k]; ok { - t.Peers[k] = append(t.Peers[k], other.Peers[k]...) - rules.Insert(other.SourceRules...) - } - } - t.SourceRules = rules.UnsortedList() -} - -func combinePeers(dest map[string][]PeerMatcher, source map[string][]PeerMatcher) map[string][]PeerMatcher { - for i, v := range source { - if _, ok := dest[i]; !ok { - dest[i] = []PeerMatcher{} - } - dest[i] = append(dest[i], v...) - } - return dest -} - // CombineTargetsIgnoringPrimaryKey creates a new v1 target from the given namespace and pod selector, // and combines all the edges and source rules from the original targets into the new target. func CombineTargetsIgnoringPrimaryKey(namespace string, podSelector metav1.LabelSelector, targets []*Target) *Target { @@ -140,10 +88,10 @@ func CombineTargetsIgnoringPrimaryKey(namespace string, podSelector metav1.Label Peers: targets[0].Peers, SourceRules: targets[0].SourceRules, } - //for _, t := range targets[1:] { - // target.Peers = append(target.Peers, t.Peers...) - // target.SourceRules = append(target.SourceRules, t.SourceRules...) - //} + for _, t := range targets[1:] { + target.Peers = append(target.Peers, t.Peers...) + target.SourceRules = append(target.SourceRules, t.SourceRules...) + } return target } @@ -181,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 { @@ -225,9 +173,12 @@ func (s *SubjectAdmin) Matches(candidate *InternalPeer) bool { func (s *SubjectAdmin) TargetString() string { if s.subject.Namespaces != nil { - return fmt.Sprintf("Namespace labels:\n%s", kube.LabelSelectorTableLines(*s.subject.Namespaces)) + namespace := kube.LabelSelectorTableLines(*s.subject.Namespaces) + return fmt.Sprintf("Namespace:\n %s", strings.TrimSpace(namespace)) } else { - return fmt.Sprintf("Namespace labels:\n%s\nPod labels:\n%s", kube.LabelSelectorTableLines(s.subject.Pods.NamespaceSelector), kube.LabelSelectorTableLines(s.subject.Pods.PodSelector)) + 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)) } } 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..81695cd2 --- /dev/null +++ b/cmd/policy-assistant/test/integration/explain_test.go @@ -0,0 +1,23 @@ +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-ravenclaw | pri=16 (example-anp-2): Allow (ineffective rules: Deny, Pass, Deny) | |\n| | | [BANP] default/default | Pod: | pri=20 (example-anp): Allow (ineffective rules: Deny, Pass, Deny) | |\n| | | | all | BANP: | |\n| | | | | (default): Allow (ineffective rules: Deny) | |\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| | | | | (default): 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| | | | | (default): Allow | |\n+---------+------------------------------------------+-----------------------------+------------------------------------------------------------------------+------------------------------------------------------------------------+----------------------------+\n| | | | | | |\n+---------+------------------------------------------+-----------------------------+------------------------------------------------------------------------+------------------------------------------------------------------------+----------------------------+\n| Egress | 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-ravenclaw | pri=16 (example-anp-2): Allow (ineffective rules: Deny, Pass, Deny) | |\n| | | [BANP] default/default | Pod: | pri=20 (example-anp): Allow (ineffective rules: Deny, Pass, Deny) | |\n| | | | all | BANP: | |\n| | | | | (default): Allow (ineffective rules: Deny, Deny) | |\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| | | | | (default): Deny | |\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| | | | | (default): Allow | |\n+---------+------------------------------------------+-----------------------------+------------------------------------------------------------------------+------------------------------------------------------------------------+----------------------------+\n" + policies := matcher.BuildV1AndV2NetPols(false, nil, examples.CoreGressRulesCombinedANB, examples.CoreGressRulesCombinedBANB) + require.Equal(t, expected, policies.ExplainTable()) + }) +} From e9a29166b64dce26e465d504077da71b5a388e4c Mon Sep 17 00:00:00 2001 From: Nikola Date: Wed, 6 Mar 2024 18:33:00 +0200 Subject: [PATCH 4/4] reviewer feedback --- cmd/policy-assistant/pkg/matcher/explain.go | 11 +- .../test/integration/explain_test.go | 153 +++++++++++++++++- 2 files changed, 159 insertions(+), 5 deletions(-) diff --git a/cmd/policy-assistant/pkg/matcher/explain.go b/cmd/policy-assistant/pkg/matcher/explain.go index 8a44d11b..3654d666 100644 --- a/cmd/policy-assistant/pkg/matcher/explain.go +++ b/cmd/policy-assistant/pkg/matcher/explain.go @@ -13,12 +13,14 @@ import ( "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 } @@ -144,9 +146,9 @@ func (s *SliceBuilder) peerProtocolGroupTableLines(t *peerProtocolGroup) { actions = append(actions, "BANP:") for _, v := range banps { if len(v.effects) > 1 { - actions = append(actions, fmt.Sprintf(" (%s): %s (ineffective rules: %s)", v.name, v.effects[0], strings.Join(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): %s", v.name, v.effects[0])) + actions = append(actions, fmt.Sprintf(" %s", v.effects[0])) } } } @@ -189,7 +191,7 @@ func groupAnbAndBanp(p []PeerMatcher) []PeerMatcher { for _, v := range p { switch t := v.(type) { case *PeerMatcherAdmin: - k := t.Port.GetPrimaryKey() + t.Pod.PrimaryKey() + 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"), @@ -217,6 +219,9 @@ func groupAnbAndBanp(p []PeerMatcher) []PeerMatcher { 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 }) diff --git a/cmd/policy-assistant/test/integration/explain_test.go b/cmd/policy-assistant/test/integration/explain_test.go index 81695cd2..f3bcb859 100644 --- a/cmd/policy-assistant/test/integration/explain_test.go +++ b/cmd/policy-assistant/test/integration/explain_test.go @@ -10,13 +10,162 @@ import ( 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" + 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-ravenclaw | pri=16 (example-anp-2): Allow (ineffective rules: Deny, Pass, Deny) | |\n| | | [BANP] default/default | Pod: | pri=20 (example-anp): Allow (ineffective rules: Deny, Pass, Deny) | |\n| | | | all | BANP: | |\n| | | | | (default): Allow (ineffective rules: Deny) | |\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| | | | | (default): 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| | | | | (default): Allow | |\n+---------+------------------------------------------+-----------------------------+------------------------------------------------------------------------+------------------------------------------------------------------------+----------------------------+\n| | | | | | |\n+---------+------------------------------------------+-----------------------------+------------------------------------------------------------------------+------------------------------------------------------------------------+----------------------------+\n| Egress | 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-ravenclaw | pri=16 (example-anp-2): Allow (ineffective rules: Deny, Pass, Deny) | |\n| | | [BANP] default/default | Pod: | pri=20 (example-anp): Allow (ineffective rules: Deny, Pass, Deny) | |\n| | | | all | BANP: | |\n| | | | | (default): Allow (ineffective rules: Deny, Deny) | |\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| | | | | (default): Deny | |\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| | | | | (default): Allow | |\n+---------+------------------------------------------+-----------------------------+------------------------------------------------------------------------+------------------------------------------------------------------------+----------------------------+\n" + 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()) })