-
Notifications
You must be signed in to change notification settings - Fork 34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for ANP and BANP in the explain command #188
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -107,19 +107,17 @@ func LabelSelectorTableLines(selector metav1.LabelSelector) string { | |
} | ||
var lines []string | ||
if len(selector.MatchLabels) > 0 { | ||
lines = append(lines, "Match labels:") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we keep this and There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or feel free to change the wording, and we can assess outputs for both the MatchLabels case and MatchExpressions There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's keep it that way. We can change it later when we are sure for the output layout. |
||
for _, key := range slice.Sort(maps.Keys(selector.MatchLabels)) { | ||
val := selector.MatchLabels[key] | ||
lines = append(lines, fmt.Sprintf(" %s: %s", key, val)) | ||
lines = append(lines, fmt.Sprintf(" %s = %s", key, val)) | ||
} | ||
} | ||
if len(selector.MatchExpressions) > 0 { | ||
lines = append(lines, "Match expressions:") | ||
sortedMatchExpressions := slice.SortOn( | ||
func(l metav1.LabelSelectorRequirement) string { return l.Key }, | ||
selector.MatchExpressions) | ||
for _, exp := range sortedMatchExpressions { | ||
lines = append(lines, fmt.Sprintf(" %s %s %+v", exp.Key, exp.Operator, slice.Sort(exp.Values))) | ||
lines = append(lines, fmt.Sprintf(" %s %s %+v", exp.Key, exp.Operator, slice.Sort(exp.Values))) | ||
} | ||
} | ||
return strings.Join(lines, "\n") | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,15 +2,36 @@ package matcher | |
|
||
import ( | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/mattfenwick/collections/pkg/json" | ||
"github.com/mattfenwick/collections/pkg/slice" | ||
"golang.org/x/exp/slices" | ||
v1 "k8s.io/api/core/v1" | ||
"strings" | ||
|
||
"github.com/mattfenwick/cyclonus/pkg/kube" | ||
"github.com/olekukonko/tablewriter" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
// peerProtocolGroup groups all anps and banps in single struct | ||
type peerProtocolGroup struct { | ||
port string | ||
subject string | ||
policies map[string]*anpGroup | ||
} | ||
|
||
// dummy implementation of the interface so we add the struct to the targer peers | ||
func (p *peerProtocolGroup) Matches(subject, peer *TrafficPeer, portInt int, portName string, protocol v1.Protocol) bool { | ||
Peac36 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return false | ||
} | ||
|
||
type anpGroup struct { | ||
name string | ||
priority int | ||
effects []string | ||
kind PolicyKind | ||
} | ||
|
||
type SliceBuilder struct { | ||
Prefix []string | ||
Elements [][]string | ||
|
@@ -26,14 +47,14 @@ func (p *Policy) ExplainTable() string { | |
table.SetAutoWrapText(false) | ||
table.SetRowLine(true) | ||
table.SetAutoMergeCells(true) | ||
// FIXME add action/priority column | ||
table.SetHeader([]string{"Type", "Subject", "Source rules", "Peer", "Port/Protocol"}) | ||
|
||
table.SetHeader([]string{"Type", "Subject", "Source rules", "Peer", "Action", "Port/Protocol"}) | ||
|
||
builder := &SliceBuilder{} | ||
ingresses, egresses := p.SortedTargets() | ||
builder.TargetsTableLines(ingresses, true) | ||
// FIXME add action/priority column | ||
builder.Elements = append(builder.Elements, []string{"", "", "", "", ""}) | ||
|
||
builder.Elements = append(builder.Elements, []string{"", "", "", "", "", ""}) | ||
builder.TargetsTableLines(egresses, false) | ||
|
||
table.AppendBulk(builder.Elements) | ||
|
@@ -50,68 +71,89 @@ func (s *SliceBuilder) TargetsTableLines(targets []*Target, isIngress bool) { | |
ruleType = "Egress" | ||
} | ||
for _, target := range targets { | ||
sourceRules := slice.Sort(target.SourceRules) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I recommend sorting all output so that it's:
|
||
sourceRules := target.SourceRules | ||
sourceRulesStrings := make([]string, 0, len(sourceRules)) | ||
for _, rule := range sourceRules { | ||
sourceRulesStrings = append(sourceRulesStrings, string(rule)) | ||
} | ||
slices.Sort(sourceRulesStrings) | ||
rules := strings.Join(sourceRulesStrings, "\n") | ||
s.Prefix = []string{ruleType, target.TargetString(), rules} | ||
|
||
if len(target.Peers) == 0 { | ||
s.Append("no pods, no ips", "no ports, no protocols") | ||
} else { | ||
for _, peer := range slice.SortOn(func(p PeerMatcher) string { return json.MustMarshalToString(p) }, target.Peers) { | ||
switch a := peer.(type) { | ||
case *PeerMatcherAdmin: | ||
s.PodPeerMatcherTableLines(a.PodPeerMatcher, a.effectFromMatch) | ||
case *AllPeersMatcher: | ||
s.Append("all pods, all ips", "all ports, all protocols") | ||
case *PortsForAllPeersMatcher: | ||
pps := PortMatcherTableLines(a.Port, NetworkPolicyV1) | ||
s.Append("all pods, all ips", strings.Join(pps, "\n")) | ||
case *IPPeerMatcher: | ||
s.IPPeerMatcherTableLines(a) | ||
case *PodPeerMatcher: | ||
s.PodPeerMatcherTableLines(a, NewV1Effect(true)) | ||
default: | ||
panic(errors.Errorf("invalid PeerMatcher type %T", a)) | ||
Peac36 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
s.Append("no pods, no ips", "NPv1: All peers allowed", "no ports, no protocols") | ||
continue | ||
} | ||
|
||
peers := groupAnbAndBanp(target.Peers) | ||
for _, p := range slice.SortOn(func(p PeerMatcher) string { return json.MustMarshalToString(p) }, peers) { | ||
switch t := p.(type) { | ||
case *AllPeersMatcher: | ||
s.Append("all pods, all ips", "NPv1: All peers allowed", "all ports, all protocols") | ||
case *PortsForAllPeersMatcher: | ||
pps := PortMatcherTableLines(t.Port, NetworkPolicyV1) | ||
s.Append("all pods, all ips", "NPv1: All peers allowed", strings.Join(pps, "\n")) | ||
case *IPPeerMatcher: | ||
s.IPPeerMatcherTableLines(t) | ||
case *PodPeerMatcher: | ||
s.Append(resolveSubject(t), "NPv1: All peers allowed", strings.Join(PortMatcherTableLines(t.Port, NewV1Effect(true).PolicyKind), "\n")) | ||
case *peerProtocolGroup: | ||
s.peerProtocolGroupTableLines(t) | ||
default: | ||
panic(errors.Errorf("invalid PeerMatcher type %T", p)) | ||
} | ||
} | ||
|
||
} | ||
} | ||
|
||
func (s *SliceBuilder) IPPeerMatcherTableLines(ip *IPPeerMatcher) { | ||
peer := ip.IPBlock.CIDR + "\n" + fmt.Sprintf("except %+v", ip.IPBlock.Except) | ||
pps := PortMatcherTableLines(ip.Port, NetworkPolicyV1) | ||
s.Append(peer, strings.Join(pps, "\n")) | ||
s.Append(peer, "NPv1: All peers allowed", strings.Join(pps, "\n")) | ||
} | ||
|
||
func (s *SliceBuilder) PodPeerMatcherTableLines(nsPodMatcher *PodPeerMatcher, e Effect) { | ||
// FIXME add action/priority column using fields of the Effect parameter "e" | ||
var namespaces string | ||
switch ns := nsPodMatcher.Namespace.(type) { | ||
case *AllNamespaceMatcher: | ||
namespaces = "all" | ||
case *LabelSelectorNamespaceMatcher: | ||
namespaces = kube.LabelSelectorTableLines(ns.Selector) | ||
// FIXME handle SameLabels, NotSameLabels | ||
case *ExactNamespaceMatcher: | ||
namespaces = ns.Namespace | ||
default: | ||
panic(errors.Errorf("invalid NamespaceMatcher type %T", ns)) | ||
func (s *SliceBuilder) peerProtocolGroupTableLines(t *peerProtocolGroup) { | ||
actions := []string{} | ||
|
||
anps := make([]*anpGroup, 0, len(t.policies)) | ||
for _, v := range t.policies { | ||
if v.kind == AdminNetworkPolicy { | ||
anps = append(anps, v) | ||
} | ||
} | ||
var pods string | ||
switch p := nsPodMatcher.Pod.(type) { | ||
case *AllPodMatcher: | ||
pods = "all" | ||
case *LabelSelectorPodMatcher: | ||
pods = kube.LabelSelectorTableLines(p.Selector) | ||
default: | ||
panic(errors.Errorf("invalid PodMatcher type %T", p)) | ||
if len(anps) > 0 { | ||
actions = append(actions, "ANP:") | ||
slices.SortFunc(anps, func(a, b *anpGroup) bool { | ||
return a.priority < b.priority | ||
}) | ||
for _, v := range anps { | ||
if len(v.effects) > 1 { | ||
actions = append(actions, fmt.Sprintf(" pri=%d (%s): %s (ineffective rules: %s)", v.priority, v.name, v.effects[0], strings.Join(v.effects[1:], ", "))) | ||
} else { | ||
actions = append(actions, fmt.Sprintf(" pri=%d (%s): %s", v.priority, v.name, v.effects[0])) | ||
} | ||
} | ||
} | ||
|
||
banps := make([]*anpGroup, 0, len(t.policies)) | ||
for _, v := range t.policies { | ||
if v.kind == BaselineAdminNetworkPolicy { | ||
banps = append(banps, v) | ||
} | ||
} | ||
if len(banps) > 0 { | ||
actions = append(actions, "BANP:") | ||
for _, v := range banps { | ||
if len(v.effects) > 1 { | ||
actions = append(actions, fmt.Sprintf(" %s (ineffective rules: %s)", v.effects[0], strings.Join(v.effects[1:], ", "))) | ||
} else { | ||
actions = append(actions, fmt.Sprintf(" %s", v.effects[0])) | ||
} | ||
} | ||
} | ||
s.Append("namespace: "+namespaces+"\n"+"pods: "+pods, strings.Join(PortMatcherTableLines(nsPodMatcher.Port, e.PolicyKind), "\n")) | ||
|
||
s.Append(t.subject, strings.Join(actions, "\n"), t.port) | ||
} | ||
|
||
func PortMatcherTableLines(pm PortMatcher, kind PolicyKind) []string { | ||
|
@@ -141,3 +183,81 @@ func PortMatcherTableLines(pm PortMatcher, kind PolicyKind) []string { | |
panic(errors.Errorf("invalid PortMatcher type %T", port)) | ||
} | ||
} | ||
|
||
func groupAnbAndBanp(p []PeerMatcher) []PeerMatcher { | ||
result := make([]PeerMatcher, 0, len(p)) | ||
groups := map[string]*peerProtocolGroup{} | ||
|
||
for _, v := range p { | ||
switch t := v.(type) { | ||
case *PeerMatcherAdmin: | ||
k := t.Port.GetPrimaryKey() + t.Pod.PrimaryKey() + t.Namespace.PrimaryKey() | ||
if _, ok := groups[k]; !ok { | ||
groups[k] = &peerProtocolGroup{ | ||
port: strings.Join(PortMatcherTableLines(t.PodPeerMatcher.Port, t.effectFromMatch.PolicyKind), "\n"), | ||
subject: resolveSubject(t.PodPeerMatcher), | ||
policies: map[string]*anpGroup{}, | ||
} | ||
} | ||
kg := t.Name | ||
if _, ok := groups[k].policies[kg]; !ok { | ||
groups[k].policies[kg] = &anpGroup{ | ||
name: t.Name, | ||
priority: t.effectFromMatch.Priority, | ||
effects: []string{}, | ||
kind: t.effectFromMatch.PolicyKind, | ||
} | ||
} | ||
groups[k].policies[kg].effects = append(groups[k].policies[kg].effects, string(t.effectFromMatch.Verdict)) | ||
default: | ||
result = append(result, v) | ||
} | ||
} | ||
|
||
groupResult := make([]*peerProtocolGroup, 0, len(groups)) | ||
for _, v := range groups { | ||
groupResult = append(groupResult, v) | ||
} | ||
slices.SortFunc(groupResult, func(a, b *peerProtocolGroup) bool { | ||
if a.port == b.port { | ||
return a.subject < b.subject | ||
} | ||
return a.port < b.port | ||
Peac36 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}) | ||
|
||
for _, v := range groupResult { | ||
result = append(result, v) | ||
} | ||
|
||
return result | ||
} | ||
|
||
func resolveSubject(nsPodMatcher *PodPeerMatcher) string { | ||
var namespaces string | ||
var pods string | ||
switch ns := nsPodMatcher.Namespace.(type) { | ||
case *AllNamespaceMatcher: | ||
namespaces = "all" | ||
case *LabelSelectorNamespaceMatcher: | ||
namespaces = kube.LabelSelectorTableLines(ns.Selector) | ||
case *SameLabelsNamespaceMatcher: | ||
namespaces = fmt.Sprintf("Same labels - %s", strings.Join(ns.labels, ", ")) | ||
case *NotSameLabelsNamespaceMatcher: | ||
namespaces = fmt.Sprintf("Not Same labels - %s", strings.Join(ns.labels, ", ")) | ||
case *ExactNamespaceMatcher: | ||
namespaces = ns.Namespace | ||
default: | ||
panic(errors.Errorf("invalid NamespaceMatcher type %T", ns)) | ||
} | ||
|
||
switch p := nsPodMatcher.Pod.(type) { | ||
case *AllPodMatcher: | ||
pods = "all" | ||
case *LabelSelectorPodMatcher: | ||
pods = kube.LabelSelectorTableLines(p.Selector) | ||
default: | ||
panic(errors.Errorf("invalid PodMatcher type %T", p)) | ||
} | ||
|
||
return fmt.Sprintf("Namespace:\n %s\nPod:\n %s", strings.TrimSpace(namespaces), strings.TrimSpace(pods)) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for a follow-up PR: set these via CLI (#201)