Skip to content

Commit 0368c76

Browse files
authored
Merge pull request #188 from Peac36/update/153
Add support for ANP and BANP in the explain command
2 parents ffefa3b + e9a2916 commit 0368c76

File tree

10 files changed

+1281
-65
lines changed

10 files changed

+1281
-65
lines changed

cmd/policy-assistant/examples/example.go

+869
Large diffs are not rendered by default.

cmd/policy-assistant/pkg/cli/analyze.go

+9-2
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@ package cli
22

33
import (
44
"fmt"
5+
"github.com/mattfenwick/cyclonus/examples"
6+
"github.com/mattfenwick/cyclonus/pkg/kube/netpol"
7+
"sigs.k8s.io/network-policy-api/apis/v1alpha1"
58
"strings"
69

710
"github.com/mattfenwick/collections/pkg/json"
811
"github.com/mattfenwick/cyclonus/pkg/connectivity/probe"
912
"github.com/mattfenwick/cyclonus/pkg/generator"
1013

1114
"github.com/mattfenwick/cyclonus/pkg/kube"
12-
"github.com/mattfenwick/cyclonus/pkg/kube/netpol"
1315
"github.com/mattfenwick/cyclonus/pkg/matcher"
1416
"github.com/mattfenwick/cyclonus/pkg/utils"
1517
"github.com/pkg/errors"
@@ -87,6 +89,8 @@ func SetupAnalyzeCommand() *cobra.Command {
8789
func RunAnalyzeCommand(args *AnalyzeArgs) {
8890
// 1. read policies from kube
8991
var kubePolicies []*networkingv1.NetworkPolicy
92+
var kubeANPs []*v1alpha1.AdminNetworkPolicy
93+
var kubeBANPs *v1alpha1.BaselineAdminNetworkPolicy
9094
var kubePods []v1.Pod
9195
var kubeNamespaces []v1.Namespace
9296
if args.AllNamespaces || len(args.Namespaces) > 0 {
@@ -118,10 +122,13 @@ func RunAnalyzeCommand(args *AnalyzeArgs) {
118122
// 3. read example policies
119123
if args.UseExamplePolicies {
120124
kubePolicies = append(kubePolicies, netpol.AllExamples...)
125+
126+
kubeANPs = examples.CoreGressRulesCombinedANB
127+
kubeBANPs = examples.CoreGressRulesCombinedBANB
121128
}
122129

123130
logrus.Debugf("parsed policies:\n%s", json.MustMarshalToString(kubePolicies))
124-
policies := matcher.BuildNetworkPolicies(args.SimplifyPolicies, kubePolicies)
131+
policies := matcher.BuildV1AndV2NetPols(args.SimplifyPolicies, kubePolicies, kubeANPs, kubeBANPs)
125132

126133
for _, mode := range args.Modes {
127134
switch mode {

cmd/policy-assistant/pkg/kube/labelselector.go

+2-4
Original file line numberDiff line numberDiff line change
@@ -107,19 +107,17 @@ func LabelSelectorTableLines(selector metav1.LabelSelector) string {
107107
}
108108
var lines []string
109109
if len(selector.MatchLabels) > 0 {
110-
lines = append(lines, "Match labels:")
111110
for _, key := range slice.Sort(maps.Keys(selector.MatchLabels)) {
112111
val := selector.MatchLabels[key]
113-
lines = append(lines, fmt.Sprintf(" %s: %s", key, val))
112+
lines = append(lines, fmt.Sprintf(" %s = %s", key, val))
114113
}
115114
}
116115
if len(selector.MatchExpressions) > 0 {
117-
lines = append(lines, "Match expressions:")
118116
sortedMatchExpressions := slice.SortOn(
119117
func(l metav1.LabelSelectorRequirement) string { return l.Key },
120118
selector.MatchExpressions)
121119
for _, exp := range sortedMatchExpressions {
122-
lines = append(lines, fmt.Sprintf(" %s %s %+v", exp.Key, exp.Operator, slice.Sort(exp.Values)))
120+
lines = append(lines, fmt.Sprintf(" %s %s %+v", exp.Key, exp.Operator, slice.Sort(exp.Values)))
123121
}
124122
}
125123
return strings.Join(lines, "\n")

cmd/policy-assistant/pkg/matcher/builder.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ func BuildTargetANP(anp *v1alpha1.AdminNetworkPolicy) (*Target, *Target) {
224224
v := AdminActionToVerdict(r.Action)
225225
matchers := BuildPeerMatcherAdmin(r.From, r.Ports)
226226
for _, m := range matchers {
227-
matcherAdmin := NewPeerMatcherANP(m, v, int(anp.Spec.Priority))
227+
matcherAdmin := NewPeerMatcherANP(m, v, int(anp.Spec.Priority), anp.Name)
228228
ingress.Peers = append(ingress.Peers, matcherAdmin)
229229
}
230230
}
@@ -240,7 +240,7 @@ func BuildTargetANP(anp *v1alpha1.AdminNetworkPolicy) (*Target, *Target) {
240240
v := AdminActionToVerdict(r.Action)
241241
matchers := BuildPeerMatcherAdmin(r.To, r.Ports)
242242
for _, m := range matchers {
243-
matcherAdmin := NewPeerMatcherANP(m, v, int(anp.Spec.Priority))
243+
matcherAdmin := NewPeerMatcherANP(m, v, int(anp.Spec.Priority), anp.Name)
244244
egress.Peers = append(egress.Peers, matcherAdmin)
245245
}
246246
}
@@ -267,7 +267,7 @@ func BuildTargetBANP(banp *v1alpha1.BaselineAdminNetworkPolicy) (*Target, *Targe
267267
v := BaselineAdminActionToVerdict(r.Action)
268268
matchers := BuildPeerMatcherAdmin(r.From, r.Ports)
269269
for _, m := range matchers {
270-
matcherAdmin := NewPeerMatcherBANP(m, v)
270+
matcherAdmin := NewPeerMatcherBANP(m, v, banp.Name)
271271
ingress.Peers = append(ingress.Peers, matcherAdmin)
272272
}
273273
}
@@ -283,7 +283,7 @@ func BuildTargetBANP(banp *v1alpha1.BaselineAdminNetworkPolicy) (*Target, *Targe
283283
v := BaselineAdminActionToVerdict(r.Action)
284284
matchers := BuildPeerMatcherAdmin(r.To, r.Ports)
285285
for _, m := range matchers {
286-
matcherAdmin := NewPeerMatcherBANP(m, v)
286+
matcherAdmin := NewPeerMatcherBANP(m, v, banp.Name)
287287
egress.Peers = append(egress.Peers, matcherAdmin)
288288
}
289289
}

cmd/policy-assistant/pkg/matcher/builder_tests.go

+12
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package matcher
22

33
import (
4+
"github.com/mattfenwick/cyclonus/examples"
45
"github.com/mattfenwick/cyclonus/pkg/kube/netpol"
56
. "github.com/onsi/ginkgo/v2"
67
. "github.com/onsi/gomega"
8+
"golang.org/x/exp/maps"
79
v1 "k8s.io/api/core/v1"
810
networkingv1 "k8s.io/api/networking/v1"
911
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -339,4 +341,14 @@ func RunBuilderTests() {
339341
}}}))
340342
})
341343
})
344+
345+
Describe("BuildV1AndV2NetPols", func() {
346+
It("it combines ANPs with same subject", func() {
347+
result := BuildV1AndV2NetPols(true, nil, examples.SimpleANPs, nil)
348+
Expect(result.Egress).To(HaveLen(1))
349+
k := maps.Keys(result.Egress)
350+
firstRule := result.Egress[k[0]]
351+
Expect(firstRule.SourceRules).To(HaveLen(2))
352+
})
353+
})
342354
}

cmd/policy-assistant/pkg/matcher/explain.go

+168-48
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,36 @@ package matcher
22

33
import (
44
"fmt"
5-
"strings"
6-
75
"github.com/mattfenwick/collections/pkg/json"
86
"github.com/mattfenwick/collections/pkg/slice"
7+
"golang.org/x/exp/slices"
8+
v1 "k8s.io/api/core/v1"
9+
"strings"
10+
911
"github.com/mattfenwick/cyclonus/pkg/kube"
1012
"github.com/olekukonko/tablewriter"
1113
"github.com/pkg/errors"
1214
)
1315

16+
// peerProtocolGroup groups all anps and banps in single struct
17+
type peerProtocolGroup struct {
18+
port string
19+
subject string
20+
policies map[string]*anpGroup
21+
}
22+
23+
// dummy implementation of the interface so we add the struct to the targer peers
24+
func (p *peerProtocolGroup) Matches(subject, peer *TrafficPeer, portInt int, portName string, protocol v1.Protocol) bool {
25+
return false
26+
}
27+
28+
type anpGroup struct {
29+
name string
30+
priority int
31+
effects []string
32+
kind PolicyKind
33+
}
34+
1435
type SliceBuilder struct {
1536
Prefix []string
1637
Elements [][]string
@@ -26,14 +47,14 @@ func (p *Policy) ExplainTable() string {
2647
table.SetAutoWrapText(false)
2748
table.SetRowLine(true)
2849
table.SetAutoMergeCells(true)
29-
// FIXME add action/priority column
30-
table.SetHeader([]string{"Type", "Subject", "Source rules", "Peer", "Port/Protocol"})
50+
51+
table.SetHeader([]string{"Type", "Subject", "Source rules", "Peer", "Action", "Port/Protocol"})
3152

3253
builder := &SliceBuilder{}
3354
ingresses, egresses := p.SortedTargets()
3455
builder.TargetsTableLines(ingresses, true)
35-
// FIXME add action/priority column
36-
builder.Elements = append(builder.Elements, []string{"", "", "", "", ""})
56+
57+
builder.Elements = append(builder.Elements, []string{"", "", "", "", "", ""})
3758
builder.TargetsTableLines(egresses, false)
3859

3960
table.AppendBulk(builder.Elements)
@@ -50,68 +71,89 @@ func (s *SliceBuilder) TargetsTableLines(targets []*Target, isIngress bool) {
5071
ruleType = "Egress"
5172
}
5273
for _, target := range targets {
53-
sourceRules := slice.Sort(target.SourceRules)
74+
sourceRules := target.SourceRules
5475
sourceRulesStrings := make([]string, 0, len(sourceRules))
5576
for _, rule := range sourceRules {
5677
sourceRulesStrings = append(sourceRulesStrings, string(rule))
5778
}
79+
slices.Sort(sourceRulesStrings)
5880
rules := strings.Join(sourceRulesStrings, "\n")
5981
s.Prefix = []string{ruleType, target.TargetString(), rules}
6082

6183
if len(target.Peers) == 0 {
62-
s.Append("no pods, no ips", "no ports, no protocols")
63-
} else {
64-
for _, peer := range slice.SortOn(func(p PeerMatcher) string { return json.MustMarshalToString(p) }, target.Peers) {
65-
switch a := peer.(type) {
66-
case *PeerMatcherAdmin:
67-
s.PodPeerMatcherTableLines(a.PodPeerMatcher, a.effectFromMatch)
68-
case *AllPeersMatcher:
69-
s.Append("all pods, all ips", "all ports, all protocols")
70-
case *PortsForAllPeersMatcher:
71-
pps := PortMatcherTableLines(a.Port, NetworkPolicyV1)
72-
s.Append("all pods, all ips", strings.Join(pps, "\n"))
73-
case *IPPeerMatcher:
74-
s.IPPeerMatcherTableLines(a)
75-
case *PodPeerMatcher:
76-
s.PodPeerMatcherTableLines(a, NewV1Effect(true))
77-
default:
78-
panic(errors.Errorf("invalid PeerMatcher type %T", a))
79-
}
84+
s.Append("no pods, no ips", "NPv1: All peers allowed", "no ports, no protocols")
85+
continue
86+
}
87+
88+
peers := groupAnbAndBanp(target.Peers)
89+
for _, p := range slice.SortOn(func(p PeerMatcher) string { return json.MustMarshalToString(p) }, peers) {
90+
switch t := p.(type) {
91+
case *AllPeersMatcher:
92+
s.Append("all pods, all ips", "NPv1: All peers allowed", "all ports, all protocols")
93+
case *PortsForAllPeersMatcher:
94+
pps := PortMatcherTableLines(t.Port, NetworkPolicyV1)
95+
s.Append("all pods, all ips", "NPv1: All peers allowed", strings.Join(pps, "\n"))
96+
case *IPPeerMatcher:
97+
s.IPPeerMatcherTableLines(t)
98+
case *PodPeerMatcher:
99+
s.Append(resolveSubject(t), "NPv1: All peers allowed", strings.Join(PortMatcherTableLines(t.Port, NewV1Effect(true).PolicyKind), "\n"))
100+
case *peerProtocolGroup:
101+
s.peerProtocolGroupTableLines(t)
102+
default:
103+
panic(errors.Errorf("invalid PeerMatcher type %T", p))
80104
}
81105
}
106+
82107
}
83108
}
84109

85110
func (s *SliceBuilder) IPPeerMatcherTableLines(ip *IPPeerMatcher) {
86111
peer := ip.IPBlock.CIDR + "\n" + fmt.Sprintf("except %+v", ip.IPBlock.Except)
87112
pps := PortMatcherTableLines(ip.Port, NetworkPolicyV1)
88-
s.Append(peer, strings.Join(pps, "\n"))
113+
s.Append(peer, "NPv1: All peers allowed", strings.Join(pps, "\n"))
89114
}
90115

91-
func (s *SliceBuilder) PodPeerMatcherTableLines(nsPodMatcher *PodPeerMatcher, e Effect) {
92-
// FIXME add action/priority column using fields of the Effect parameter "e"
93-
var namespaces string
94-
switch ns := nsPodMatcher.Namespace.(type) {
95-
case *AllNamespaceMatcher:
96-
namespaces = "all"
97-
case *LabelSelectorNamespaceMatcher:
98-
namespaces = kube.LabelSelectorTableLines(ns.Selector)
99-
// FIXME handle SameLabels, NotSameLabels
100-
case *ExactNamespaceMatcher:
101-
namespaces = ns.Namespace
102-
default:
103-
panic(errors.Errorf("invalid NamespaceMatcher type %T", ns))
116+
func (s *SliceBuilder) peerProtocolGroupTableLines(t *peerProtocolGroup) {
117+
actions := []string{}
118+
119+
anps := make([]*anpGroup, 0, len(t.policies))
120+
for _, v := range t.policies {
121+
if v.kind == AdminNetworkPolicy {
122+
anps = append(anps, v)
123+
}
104124
}
105-
var pods string
106-
switch p := nsPodMatcher.Pod.(type) {
107-
case *AllPodMatcher:
108-
pods = "all"
109-
case *LabelSelectorPodMatcher:
110-
pods = kube.LabelSelectorTableLines(p.Selector)
111-
default:
112-
panic(errors.Errorf("invalid PodMatcher type %T", p))
125+
if len(anps) > 0 {
126+
actions = append(actions, "ANP:")
127+
slices.SortFunc(anps, func(a, b *anpGroup) bool {
128+
return a.priority < b.priority
129+
})
130+
for _, v := range anps {
131+
if len(v.effects) > 1 {
132+
actions = append(actions, fmt.Sprintf(" pri=%d (%s): %s (ineffective rules: %s)", v.priority, v.name, v.effects[0], strings.Join(v.effects[1:], ", ")))
133+
} else {
134+
actions = append(actions, fmt.Sprintf(" pri=%d (%s): %s", v.priority, v.name, v.effects[0]))
135+
}
136+
}
137+
}
138+
139+
banps := make([]*anpGroup, 0, len(t.policies))
140+
for _, v := range t.policies {
141+
if v.kind == BaselineAdminNetworkPolicy {
142+
banps = append(banps, v)
143+
}
144+
}
145+
if len(banps) > 0 {
146+
actions = append(actions, "BANP:")
147+
for _, v := range banps {
148+
if len(v.effects) > 1 {
149+
actions = append(actions, fmt.Sprintf(" %s (ineffective rules: %s)", v.effects[0], strings.Join(v.effects[1:], ", ")))
150+
} else {
151+
actions = append(actions, fmt.Sprintf(" %s", v.effects[0]))
152+
}
153+
}
113154
}
114-
s.Append("namespace: "+namespaces+"\n"+"pods: "+pods, strings.Join(PortMatcherTableLines(nsPodMatcher.Port, e.PolicyKind), "\n"))
155+
156+
s.Append(t.subject, strings.Join(actions, "\n"), t.port)
115157
}
116158

117159
func PortMatcherTableLines(pm PortMatcher, kind PolicyKind) []string {
@@ -141,3 +183,81 @@ func PortMatcherTableLines(pm PortMatcher, kind PolicyKind) []string {
141183
panic(errors.Errorf("invalid PortMatcher type %T", port))
142184
}
143185
}
186+
187+
func groupAnbAndBanp(p []PeerMatcher) []PeerMatcher {
188+
result := make([]PeerMatcher, 0, len(p))
189+
groups := map[string]*peerProtocolGroup{}
190+
191+
for _, v := range p {
192+
switch t := v.(type) {
193+
case *PeerMatcherAdmin:
194+
k := t.Port.GetPrimaryKey() + t.Pod.PrimaryKey() + t.Namespace.PrimaryKey()
195+
if _, ok := groups[k]; !ok {
196+
groups[k] = &peerProtocolGroup{
197+
port: strings.Join(PortMatcherTableLines(t.PodPeerMatcher.Port, t.effectFromMatch.PolicyKind), "\n"),
198+
subject: resolveSubject(t.PodPeerMatcher),
199+
policies: map[string]*anpGroup{},
200+
}
201+
}
202+
kg := t.Name
203+
if _, ok := groups[k].policies[kg]; !ok {
204+
groups[k].policies[kg] = &anpGroup{
205+
name: t.Name,
206+
priority: t.effectFromMatch.Priority,
207+
effects: []string{},
208+
kind: t.effectFromMatch.PolicyKind,
209+
}
210+
}
211+
groups[k].policies[kg].effects = append(groups[k].policies[kg].effects, string(t.effectFromMatch.Verdict))
212+
default:
213+
result = append(result, v)
214+
}
215+
}
216+
217+
groupResult := make([]*peerProtocolGroup, 0, len(groups))
218+
for _, v := range groups {
219+
groupResult = append(groupResult, v)
220+
}
221+
slices.SortFunc(groupResult, func(a, b *peerProtocolGroup) bool {
222+
if a.port == b.port {
223+
return a.subject < b.subject
224+
}
225+
return a.port < b.port
226+
})
227+
228+
for _, v := range groupResult {
229+
result = append(result, v)
230+
}
231+
232+
return result
233+
}
234+
235+
func resolveSubject(nsPodMatcher *PodPeerMatcher) string {
236+
var namespaces string
237+
var pods string
238+
switch ns := nsPodMatcher.Namespace.(type) {
239+
case *AllNamespaceMatcher:
240+
namespaces = "all"
241+
case *LabelSelectorNamespaceMatcher:
242+
namespaces = kube.LabelSelectorTableLines(ns.Selector)
243+
case *SameLabelsNamespaceMatcher:
244+
namespaces = fmt.Sprintf("Same labels - %s", strings.Join(ns.labels, ", "))
245+
case *NotSameLabelsNamespaceMatcher:
246+
namespaces = fmt.Sprintf("Not Same labels - %s", strings.Join(ns.labels, ", "))
247+
case *ExactNamespaceMatcher:
248+
namespaces = ns.Namespace
249+
default:
250+
panic(errors.Errorf("invalid NamespaceMatcher type %T", ns))
251+
}
252+
253+
switch p := nsPodMatcher.Pod.(type) {
254+
case *AllPodMatcher:
255+
pods = "all"
256+
case *LabelSelectorPodMatcher:
257+
pods = kube.LabelSelectorTableLines(p.Selector)
258+
default:
259+
panic(errors.Errorf("invalid PodMatcher type %T", p))
260+
}
261+
262+
return fmt.Sprintf("Namespace:\n %s\nPod:\n %s", strings.TrimSpace(namespaces), strings.TrimSpace(pods))
263+
}

0 commit comments

Comments
 (0)