Skip to content
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

[WIP] add 'graph' analysis mode #117

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions pkg/cli/analyze.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const (
QueryTrafficMode = "query-traffic"
QueryTargetMode = "query-target"
ProbeMode = "probe"
GraphMode = "graph"
)

var AllModes = []string{
Expand All @@ -37,6 +38,7 @@ var AllModes = []string{
QueryTrafficMode,
QueryTargetMode,
ProbeMode,
GraphMode,
}

type AnalyzeArgs struct {
Expand Down Expand Up @@ -141,6 +143,10 @@ func RunAnalyzeCommand(args *AnalyzeArgs) {
QueryTraffic(policies, args.TrafficPath)
case ProbeMode:
ProbeSyntheticConnectivity(policies, args.ProbePath, kubePods, kubeNamespaces)
case GraphMode:
graph := matcher.BuildGraph(policies)
logrus.Debugf("graph:\n%s", graph)
fmt.Println(graph)
default:
panic(errors.Errorf("unrecognized mode %s", mode))
}
Expand Down
141 changes: 141 additions & 0 deletions pkg/matcher/dotgraph.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package matcher

import (
"fmt"
"github.com/mattfenwick/cyclonus/pkg/kube"
"github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"strings"
)

type Graph struct {
Nodes map[string]bool
Edges []*Edge
}

func NewGraph() *Graph {
return &Graph{Nodes: map[string]bool{}}
}

func (g *Graph) AddNode(node string) {
g.Nodes[node] = true
}

func (g *Graph) AddEdge(from string, to string) {
g.Edges = append(g.Edges, &Edge{From: from, To: to})
}

type Edge struct {
From string
To string
}

func (g *Graph) Serialize() string {
lines := []string{`digraph "netpols" {`}
for node := range g.Nodes {
lines = append(lines, fmt.Sprintf(` "%s" [color=%s,penwidth=5];`, node, "red"))
}
for _, edge := range g.Edges {
lines = append(lines, fmt.Sprintf(` "%s" -> "%s";`, edge.From, edge.To))
}
return strings.Join(append(lines, "}"), "\n")
}

func BuildGraph(np *Policy) string {
graph := NewGraph()
// TODO add egress
for _, rule := range np.Ingress {
name := rule.Namespace + "/" + LabelSelectorGraph(rule.PodSelector)
graph.AddNode(name)
for _, peer := range rule.Peers {
from := PeerMatcherGraph(peer)
graph.AddEdge(from, name)
}
}
return graph.Serialize()
}

func PeerMatcherGraph(peer PeerMatcher) string {
switch a := peer.(type) {
case *AllPeersMatcher:
return "any"
case *PortsForAllPeersMatcher:
return "all pod/ip " + PortMatcherGraph(a.Port)
case *IPPeerMatcher:
return IPPeerMatcherGraph(a)
case *PodPeerMatcher:
return PodPeerMatcherGraph(a)
default:
panic(errors.Errorf("invalid PeerMatcher type %T", a))
}
}

func IPPeerMatcherGraph(ip *IPPeerMatcher) string {
peer := fmt.Sprintf("%s except %+v", ip.IPBlock.CIDR, ip.IPBlock.Except)
return fmt.Sprintf("%s@%s", peer, PortMatcherGraph(ip.Port))
}

func PodPeerMatcherGraph(nsPodMatcher *PodPeerMatcher) string {
var namespaces string
switch ns := nsPodMatcher.Namespace.(type) {
case *AllNamespaceMatcher:
namespaces = "all"
case *LabelSelectorNamespaceMatcher:
namespaces = LabelSelectorGraph(ns.Selector)
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 = LabelSelectorGraph(p.Selector)
default:
panic(errors.Errorf("invalid PodMatcher type %T", p))
}
return fmt.Sprintf("ns: %s; pod: %s; port: %s", namespaces, pods, PortMatcherGraph(nsPodMatcher.Port))
}

func LabelSelectorGraph(selector metav1.LabelSelector) string {
if kube.IsLabelSelectorEmpty(selector) {
return "all"
}
var kvs []string
if len(selector.MatchLabels) > 0 {
for key, val := range selector.MatchLabels {
kvs = append(kvs, fmt.Sprintf("[%s: %s]", key, val))
}
}
var exps []string
if len(selector.MatchExpressions) > 0 {
for _, exp := range selector.MatchExpressions {
exps = append(exps, fmt.Sprintf("(%s %s %+v)", exp.Key, exp.Operator, exp.Values))
}
}
return strings.Join(append(kvs, exps...), ", ")
}

func PortMatcherGraph(pm PortMatcher) string {
switch port := pm.(type) {
case *AllPortMatcher:
return "any"
case *SpecificPortMatcher:
var pps []string
for _, portProtocol := range port.Ports {
if portProtocol.Port == nil {
pps = append(pps, "any/"+string(portProtocol.Protocol))
} else {
pps = append(pps, fmt.Sprintf("%s/%s", portProtocol.Port.String(), string(portProtocol.Protocol)))
}
}
for _, portRange := range port.PortRanges {
pps = append(pps, fmt.Sprintf("[%d-%d]/%s", portRange.From, portRange.To, portRange.Protocol))
}
return strings.Join(pps, ", ")
default:
panic(errors.Errorf("invalid PortMatcher type %T", port))
}
}
38 changes: 21 additions & 17 deletions pkg/matcher/explain.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,31 +57,35 @@ func (s *SliceBuilder) TargetsTableLines(targets []*Target, isIngress bool) {
s.Append("no pods, no ips", "no ports, no protocols")
} else {
for _, peer := range target.Peers {
switch a := peer.(type) {
case *AllPeersMatcher:
s.Append("all pods, all ips", "all ports, all protocols")
case *PortsForAllPeersMatcher:
pps := PortMatcherTableLines(a.Port)
s.Append("all pods, all ips", strings.Join(pps, "\n"))
case *IPPeerMatcher:
s.IPPeerMatcherTableLines(a)
case *PodPeerMatcher:
s.PodPeerMatcherTableLines(a)
default:
panic(errors.Errorf("invalid PeerMatcher type %T", a))
}
s.Append(PeerMatcherTableLines(peer)...)
}
}
}
}

func (s *SliceBuilder) IPPeerMatcherTableLines(ip *IPPeerMatcher) {
func PeerMatcherTableLines(peer PeerMatcher) []string {
switch a := peer.(type) {
case *AllPeersMatcher:
return []string{"all pods, all ips", "all ports, all protocols"}
case *PortsForAllPeersMatcher:
pps := PortMatcherTableLines(a.Port)
return []string{"all pods, all ips", strings.Join(pps, "\n")}
case *IPPeerMatcher:
return IPPeerMatcherTableLines(a)
case *PodPeerMatcher:
return PodPeerMatcherTableLines(a)
default:
panic(errors.Errorf("invalid PeerMatcher type %T", a))
}
}

func IPPeerMatcherTableLines(ip *IPPeerMatcher) []string {
peer := ip.IPBlock.CIDR + "\n" + fmt.Sprintf("except %+v", ip.IPBlock.Except)
pps := PortMatcherTableLines(ip.Port)
s.Append(peer, strings.Join(pps, "\n"))
return []string{peer, strings.Join(pps, "\n")}
}

func (s *SliceBuilder) PodPeerMatcherTableLines(nsPodMatcher *PodPeerMatcher) {
func PodPeerMatcherTableLines(nsPodMatcher *PodPeerMatcher) []string {
var namespaces string
switch ns := nsPodMatcher.Namespace.(type) {
case *AllNamespaceMatcher:
Expand All @@ -102,7 +106,7 @@ func (s *SliceBuilder) PodPeerMatcherTableLines(nsPodMatcher *PodPeerMatcher) {
default:
panic(errors.Errorf("invalid PodMatcher type %T", p))
}
s.Append("namespace: "+namespaces+"\n"+"pods: "+pods, strings.Join(PortMatcherTableLines(nsPodMatcher.Port), "\n"))
return []string{"namespace: " + namespaces + "\n" + "pods: " + pods, strings.Join(PortMatcherTableLines(nsPodMatcher.Port), "\n")}
}

func PortMatcherTableLines(pm PortMatcher) []string {
Expand Down