Skip to content

Commit

Permalink
add support for anp and banp in the explain command
Browse files Browse the repository at this point in the history
  • Loading branch information
Peac36 committed Jan 14, 2024
1 parent 95ae2f8 commit 52f1f41
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 28 deletions.
39 changes: 37 additions & 2 deletions cmd/policy-assistant/pkg/cli/analyze.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@ package cli

import (
"fmt"
"github.com/mattfenwick/cyclonus/pkg/kube/netpol"
"io"
"k8s.io/apimachinery/pkg/util/yaml"
"os"
"sigs.k8s.io/network-policy-api/apis/v1alpha1"
"strings"

"github.com/mattfenwick/collections/pkg/json"
"github.com/mattfenwick/cyclonus/pkg/connectivity/probe"
"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"
Expand Down Expand Up @@ -87,6 +91,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 {
Expand Down Expand Up @@ -118,10 +124,18 @@ func RunAnalyzeCommand(args *AnalyzeArgs) {
// 3. read example policies
if args.UseExamplePolicies {
kubePolicies = append(kubePolicies, netpol.AllExamples...)

// remove and add appropriate examples like v1 policies
kubeANPs, _ = loadAndParse[v1alpha1.AdminNetworkPolicy]("./../../../../conformance/base/admin_network_policy/core-gress-rules-combined.yaml")
b, err := loadAndParse[v1alpha1.BaselineAdminNetworkPolicy]("./../../../../conformance/base/baseline_admin_network_policy/core-gress-rules-combined.yaml")
if err == nil && len(b) > 0 {
kubeBANPs = b[0]
}

}

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 {
Expand Down Expand Up @@ -296,3 +310,24 @@ func ProbeSyntheticConnectivity(explainedPolicies *matcher.Policy, modelPath str
fmt.Printf("Egress:\n%s\n", simulatedProbe.RenderEgress())
fmt.Printf("Combined:\n%s\n\n\n", simulatedProbe.RenderTable())
}

func loadAndParse[T any](path string) ([]*T, error) {
t, err := os.Open(path)
var out []*T
if err != nil {
return out, err
}
decoder := yaml.NewYAMLOrJSONDecoder(t, 4096)

for {
anp := new(T)
err := decoder.Decode(anp)
if err != nil {
if errors.Is(err, io.EOF) {
break
}
}
out = append(out, anp)
}
return out, nil
}
4 changes: 1 addition & 3 deletions cmd/policy-assistant/pkg/kube/labelselector.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 4 additions & 4 deletions cmd/policy-assistant/pkg/matcher/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand All @@ -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)
}
}
Expand All @@ -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)
}
}
Expand All @@ -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)
}
}
Expand Down
36 changes: 23 additions & 13 deletions cmd/policy-assistant/pkg/matcher/explain.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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)
Expand All @@ -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))
Expand All @@ -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)
case *AllPeersMatcher:
s.Append("all pods, all ips", "all ports, all protocols")
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"))
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))
continue
}
}
}
Expand All @@ -85,11 +85,10 @@ 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"
var namespaces string
switch ns := nsPodMatcher.Namespace.(type) {
case *AllNamespaceMatcher:
Expand All @@ -111,7 +110,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 labels:\n %s\nPod labels:\n %s", strings.TrimSpace(namespaces), strings.TrimSpace(pods)), priorityTableLine(e), strings.Join(PortMatcherTableLines(nsPodMatcher.Port, e.PolicyKind), "\n"))
}

func PortMatcherTableLines(pm PortMatcher, kind PolicyKind) []string {
Expand Down Expand Up @@ -141,3 +140,14 @@ func PortMatcherTableLines(pm PortMatcher, kind PolicyKind) []string {
panic(errors.Errorf("invalid PortMatcher type %T", port))
}
}

func priorityTableLine(e Effect) string {
if e.PolicyKind == NetworkPolicyV1 {
return "All peers allowed"
} else if e.PolicyKind == AdminNetworkPolicy {
return fmt.Sprintf("%s (%s): %s (pri=%d)", e.PolicyKind, e.Source, e.Verdict, e.Priority)
} else {
return fmt.Sprintf("%s (%s): %s", e.PolicyKind, e.Source, e.Verdict)
}

}
11 changes: 7 additions & 4 deletions cmd/policy-assistant/pkg/matcher/peermatcherv2.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,26 @@ type PeerMatcherAdmin struct {
}

// 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,
effectFromMatch: Effect{
PolicyKind: AdminNetworkPolicy,
Priority: priority,
Verdict: v,
Source: source,
},
}
}

// 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,
effectFromMatch: Effect{
PolicyKind: BaselineAdminNetworkPolicy,
Verdict: v,
Source: source,
},
}
}
Expand All @@ -42,6 +44,7 @@ type Effect struct {
// Priority is only used for ANP (there can only be one BANP)
Priority int
Verdict
Source string
}

type PolicyKind string
Expand All @@ -54,9 +57,9 @@ const (

func NewV1Effect(allow bool) Effect {
if allow {
return Effect{NetworkPolicyV1, 0, Allow}
return Effect{NetworkPolicyV1, 0, Allow, ""}
}
return Effect{NetworkPolicyV1, 0, None}
return Effect{NetworkPolicyV1, 0, None, ""}
}

type Verdict string
Expand Down
7 changes: 5 additions & 2 deletions cmd/policy-assistant/pkg/matcher/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 52f1f41

Please sign in to comment.