Skip to content

Commit

Permalink
Feature/service map (#35)
Browse files Browse the repository at this point in the history
  • Loading branch information
RomainVernoux committed Nov 3, 2020
1 parent 8079947 commit 3c5f90e
Show file tree
Hide file tree
Showing 43 changed files with 2,931 additions and 1,312 deletions.
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,20 @@

# Karto

A simple static analysis tool to explore and diagnosticate network policies declared in a Kubernetes cluster.
A simple static analysis tool to explore a Kubernetes cluster.

![demo](docs/assets/demo.gif)
## Observe your cluster state, in real time!

![deployment-demo](docs/assets/deployment-demo.gif)

## Diagnosticate network policies

![network-policy-demo](docs/assets/network-policy-demo.gif)

## Main features

The left part of the screen contains the controls for the main view:
- View: choose your view (workload or network policies)
- Filters: filter pods by namespace, labels and name
- Include ingress neighbors: display pods that can reach those in the current selection
- Include egress neighbors: display pods that can be reached by those in the current selection
Expand All @@ -21,14 +28,7 @@ The main view shows the graph of pods and allowed routes in your selection:
- Zoom in and out by scrolling
- Drag and drop pods to draw the perfect map of your cluster

Hover over a pod to display details:
- Name, namespace and labels
- Isolation (ingress/egress)

Hover over a route to investigate allowed traffic:
- Source/target pod
- Ports
- Explanation (lack of isolation or network policies allowing traffic)
Hover over an item to display details: name, namespace, labels, isolation (ingress/egress)... and more!

## Installation

Expand Down
129 changes: 129 additions & 0 deletions analyzer/analyzer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package analyzer

import (
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
_ "k8s.io/client-go/plugin/pkg/client/auth"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/workqueue"
"karto/analyzer/pod"
"karto/analyzer/traffic"
"karto/analyzer/workload"
"karto/types"
"log"
"time"
)

type clusterState struct {
Namespaces []*corev1.Namespace
Pods []*corev1.Pod
Services []*corev1.Service
ReplicaSets []*appsv1.ReplicaSet
Deployments []*appsv1.Deployment
Policies []*networkingv1.NetworkPolicy
}

func AnalyzeOnChange(k8sConfigPath string, resultsChannel chan<- types.AnalysisResult) {
k8sClient := getK8sClient(k8sConfigPath)
analyzeQueue := workqueue.NewRateLimitingQueue(workqueue.DefaultItemBasedRateLimiter())
informerFactory := informers.NewSharedInformerFactory(k8sClient, 0)
namespacesInformer := informerFactory.Core().V1().Namespaces()
podInformer := informerFactory.Core().V1().Pods()
servicesInformer := informerFactory.Core().V1().Services()
replicaSetsInformer := informerFactory.Apps().V1().ReplicaSets()
deploymentsInformer := informerFactory.Apps().V1().Deployments()
policiesInformer := informerFactory.Networking().V1().NetworkPolicies()
eventHandler := cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) { analyzeQueue.Add(nil) },
UpdateFunc: func(oldObj, newObj interface{}) { analyzeQueue.Add(nil) },
DeleteFunc: func(obj interface{}) { analyzeQueue.Add(nil) },
}
namespacesInformer.Informer().AddEventHandler(eventHandler)
podInformer.Informer().AddEventHandler(eventHandler)
servicesInformer.Informer().AddEventHandler(eventHandler)
replicaSetsInformer.Informer().AddEventHandler(eventHandler)
deploymentsInformer.Informer().AddEventHandler(eventHandler)
policiesInformer.Informer().AddEventHandler(eventHandler)
informerFactory.Start(wait.NeverStop)
informerFactory.WaitForCacheSync(wait.NeverStop)
for {
obj, _ := analyzeQueue.Get()
namespaces, err := namespacesInformer.Lister().List(labels.Everything())
if err != nil {
panic(err.Error())
}
pods, err := podInformer.Lister().List(labels.Everything())
if err != nil {
panic(err.Error())
}
services, err := servicesInformer.Lister().List(labels.Everything())
if err != nil {
panic(err.Error())
}
replicaSets, err := replicaSetsInformer.Lister().List(labels.Everything())
if err != nil {
panic(err.Error())
}
deployments, err := deploymentsInformer.Lister().List(labels.Everything())
if err != nil {
panic(err.Error())
}
policies, err := policiesInformer.Lister().List(labels.Everything())
if err != nil {
panic(err.Error())
}
resultsChannel <- analyze(clusterState{
Namespaces: namespaces,
Pods: pods,
Services: services,
ReplicaSets: replicaSets,
Deployments: deployments,
Policies: policies,
})
analyzeQueue.Forget(obj)
analyzeQueue.Done(obj)
}
}

func getK8sClient(k8sClientConfig string) *kubernetes.Clientset {
var config *rest.Config
var err1InsideCluster, errOutsideCluster error
config, err1InsideCluster = rest.InClusterConfig()
if err1InsideCluster != nil {
log.Println("Unable to connect to Kubernetes service, fallback to kubeconfig file")
config, errOutsideCluster = clientcmd.BuildConfigFromFlags("", k8sClientConfig)
if errOutsideCluster != nil {
panic(errOutsideCluster.Error())
}
}
k8sClient, err := kubernetes.NewForConfig(config)
if err != nil {
panic(err.Error())
}
return k8sClient
}

func analyze(clusterState clusterState) types.AnalysisResult {
start := time.Now()
pods := pod.Analyze(clusterState.Pods)
podIsolations, allowedRoutes := traffic.Analyze(clusterState.Pods, clusterState.Namespaces, clusterState.Policies)
services, replicaSets, deployments := workload.Analyze(clusterState.Pods, clusterState.Services, clusterState.ReplicaSets, clusterState.Deployments)
elapsed := time.Since(start)
log.Printf("Finished analysis in %s, found: %d pods, %d allowed routes, %d services, %d replicaSets and %d deployments\n",
elapsed, len(pods), len(allowedRoutes), len(services), len(replicaSets), len(deployments))
return types.AnalysisResult{
Pods: pods,
PodIsolations: podIsolations,
AllowedRoutes: allowedRoutes,
Services: services,
ReplicaSets: replicaSets,
Deployments: deployments,
}
}
26 changes: 26 additions & 0 deletions analyzer/pod/pod_analyzer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package pod

import (
corev1 "k8s.io/api/core/v1"
"karto/types"
)

func Analyze(pods []*corev1.Pod) []types.Pod {
return toPods(pods)
}

func toPods(pods []*corev1.Pod) []types.Pod {
result := make([]types.Pod, 0)
for _, podIsolation := range pods {
result = append(result, toPod(podIsolation))
}
return result
}

func toPod(pod *corev1.Pod) types.Pod {
return types.Pod{
Name: pod.Name,
Namespace: pod.Namespace,
Labels: pod.Labels,
}
}
Loading

0 comments on commit 3c5f90e

Please sign in to comment.