kubearmor-client/probe/probe.go
Manik Mehta a5af11a35f
Karmor support for windows and removal of cilium (#471)
* windows support

Signed-off-by: Manik2708 <mehtamanik96@gmail.com>

* cilium removed

Signed-off-by: Manik2708 <mehtamanik96@gmail.com>

* some changes reverted

Signed-off-by: Manik2708 <mehtamanik96@gmail.com>

* add local-release to makefile

Signed-off-by: Aryan-sharma11 <aryan1126.sharma@gmail.com>

* remove contains element

Signed-off-by: Aryan-sharma11 <aryan1126.sharma@gmail.com>

* fix snyk failures and bump dependencies

Signed-off-by: Aryan-sharma11 <aryan1126.sharma@gmail.com>

---------

Signed-off-by: Manik2708 <mehtamanik96@gmail.com>
Signed-off-by: Aryan-sharma11 <aryan1126.sharma@gmail.com>
Co-authored-by: Aryan-sharma11 <aryan1126.sharma@gmail.com>
2025-01-16 17:03:45 +05:30

414 lines
13 KiB
Go

// SPDX-License-Identifier: Apache-2.0
// Copyright 2022 Authors of KubeArmor
// Package probe helps check compatibility of KubeArmor in a given environment
// Don't import any unix or windows specific package in this file. This file is commonly shared by
// both windows and unix platforms and importing any platform specific packages here will lead to compilation
// errors in both platforms.
package probe
import (
"context"
"encoding/json"
"fmt"
"io"
"log"
"sort"
"strconv"
"strings"
mapset "github.com/deckarep/golang-set/v2"
"github.com/fatih/color"
jsoniter "github.com/json-iterator/go"
tp "github.com/kubearmor/KubeArmor/KubeArmor/types"
"github.com/kubearmor/kubearmor-client/k8s"
"golang.org/x/exp/slices"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/tools/remotecommand"
"errors"
)
var white = color.New(color.FgWhite)
var boldWhite = white.Add(color.Bold)
var green = color.New(color.FgGreen)
var itwhite = color.New(color.Italic).Add(color.Italic)
var red = color.New(color.FgRed)
var yellow = color.New(color.FgYellow)
var blue = color.New(color.FgBlue)
var ErrKubeArmorNotRunningOnK8s = errors.New("kubearmor is not running in k8s")
func PrintProbeResultCmd(c *k8s.Client, o Options) error {
return printProbeResult(c, o)
}
func printWhenKubeArmorIsRunningInK8s(c *k8s.Client, o Options, daemonsetStatus *Status) error {
deploymentData := getKubeArmorDeployments(c)
containerData := getKubeArmorContainers(c)
probeData, nodeData, err := ProbeRunningKubeArmorNodes(c, o)
if err != nil {
log.Println("error occured when probing kubearmor nodes", err)
}
postureData := getPostureData(probeData)
armoredPodData, podData, err := getAnnotatedPods(c, o, postureData)
if err != nil {
log.Println("error occured when getting annotated pods", err)
}
if o.Output == "json" {
ProbeData := map[string]interface{}{"Probe Data": map[string]interface{}{
"DaemonsetStatus": daemonsetStatus,
"Deployments": deploymentData,
"Containers": containerData,
"Nodes": nodeData,
"ArmoredPods": armoredPodData,
},
}
out, err := json.Marshal(ProbeData)
if err != nil {
return err
}
o.printLn(string(out))
} else {
o.printDaemonsetData(daemonsetStatus)
o.printKubearmorDeployments(deploymentData)
o.printKubeArmorContainers(containerData)
o.printKubeArmorprobe(probeData)
o.printAnnotatedPods(podData)
}
return nil
}
func isKubeArmorRunning(c *k8s.Client) (bool, *Status) {
isRunning, DaemonsetStatus := getKubeArmorDaemonset(c)
return isRunning, DaemonsetStatus
}
func getKubeArmorDaemonset(c *k8s.Client) (bool, *Status) {
// KubeArmor DaemonSet
w, err := c.K8sClientset.AppsV1().DaemonSets("").List(context.Background(), metav1.ListOptions{
LabelSelector: "kubearmor-app=kubearmor",
})
if err != nil {
log.Println("error when getting kubearmor daemonset", err)
return false, nil
}
if len(w.Items) == 0 {
return false, nil
}
desired, ready, available := w.Items[0].Status.DesiredNumberScheduled, w.Items[0].Status.NumberReady, w.Items[0].Status.NumberAvailable
if desired != ready && desired != available && ready == 0 {
// set kubearmor to not running only if there are 0 ready pods
return false, nil
}
DaemonSetStatus := Status{
Desired: strconv.Itoa(int(desired)),
Ready: strconv.Itoa(int(ready)),
Available: strconv.Itoa(int(available)),
}
return true, &DaemonSetStatus
}
func getKubeArmorDeployments(c *k8s.Client) map[string]*Status {
kubearmorDeployments, err := c.K8sClientset.AppsV1().Deployments("").List(context.Background(), metav1.ListOptions{
LabelSelector: "kubearmor-app",
})
if err != nil {
log.Println("error while getting kubearmor deployments", err)
return nil
}
if len(kubearmorDeployments.Items) > 0 {
DeploymentsData := make(map[string]*Status)
for _, kubearmorDeploymentItem := range kubearmorDeployments.Items {
desired, ready, available := kubearmorDeploymentItem.Status.UpdatedReplicas, kubearmorDeploymentItem.Status.ReadyReplicas, kubearmorDeploymentItem.Status.AvailableReplicas
if desired == ready && desired == available {
DeploymentsData[kubearmorDeploymentItem.Name] = &Status{
Desired: strconv.Itoa(int(desired)),
Ready: strconv.Itoa(int(ready)),
Available: strconv.Itoa(int(available)),
}
}
}
return DeploymentsData
}
return nil
}
func getKubeArmorContainers(c *k8s.Client) map[string]*KubeArmorPodSpec {
kubearmorPods, err := c.K8sClientset.CoreV1().Pods("").List(context.Background(), metav1.ListOptions{
LabelSelector: "kubearmor-app",
})
if err != nil {
log.Println("error occured when getting kubearmor pods", err)
return nil
}
KAContainerData := make(map[string]*KubeArmorPodSpec)
if len(kubearmorPods.Items) > 0 {
for _, kubearmorPodItem := range kubearmorPods.Items {
KAContainerData[kubearmorPodItem.Name] = &KubeArmorPodSpec{
Running: strconv.Itoa(len(kubearmorPodItem.Spec.Containers)),
Image_Version: kubearmorPodItem.Spec.Containers[0].Image,
}
}
return KAContainerData
}
return nil
}
// ProbeRunningKubeArmorNodes extracts data from running KubeArmor daemonset by executing into the container and reading /tmp/kubearmor.cfg
func ProbeRunningKubeArmorNodes(c *k8s.Client, o Options) ([]KubeArmorProbeData, map[string]KubeArmorProbeData, error) {
// KubeArmor Nodes
pods, err := c.K8sClientset.CoreV1().Pods("").List(context.Background(), metav1.ListOptions{
LabelSelector: "kubearmor-app=kubearmor",
})
if err != nil || len(pods.Items) == 0 {
return []KubeArmorProbeData{}, nil, fmt.Errorf("no nodes found")
}
nodeData := make(map[string]KubeArmorProbeData)
var dataList []KubeArmorProbeData
for i, item := range pods.Items {
if item.Status.Phase != corev1.PodRunning {
continue
}
data, err := readDataFromKubeArmor(c, item)
if err != nil {
continue
}
dataList = append(dataList, data)
nodeData["Node"+strconv.Itoa(i+1)] = data
}
return dataList, nodeData, nil
}
func readDataFromKubeArmor(c *k8s.Client, pod corev1.Pod) (KubeArmorProbeData, error) {
srcPath := "/tmp/karmorProbeData.cfg"
reader, outStream := io.Pipe()
cmdArr := []string{"cat", srcPath}
req := c.K8sClientset.CoreV1().RESTClient().
Get().
Namespace(pod.Namespace).
Resource("pods").
Name(pod.Name).
SubResource("exec").
VersionedParams(&corev1.PodExecOptions{
Container: pod.Spec.Containers[0].Name,
Command: cmdArr,
Stdin: false,
Stdout: true,
Stderr: false,
TTY: false,
}, scheme.ParameterCodec)
exec, err := remotecommand.NewSPDYExecutor(c.Config, "POST", req.URL())
if err != nil {
return KubeArmorProbeData{}, err
}
go func() {
defer outStream.Close()
err = exec.StreamWithContext(context.TODO(), remotecommand.StreamOptions{
Stdout: outStream,
Tty: false,
})
}()
buf, err := io.ReadAll(reader)
if err != nil {
return KubeArmorProbeData{}, fmt.Errorf("error occured while reading data from kubeArmor pod %s", err.Error())
}
if len(buf) == 0 {
return KubeArmorProbeData{}, fmt.Errorf("read empty data from kubearmor pod")
}
var kd KubeArmorProbeData
var json = jsoniter.ConfigCompatibleWithStandardLibrary
err = json.Unmarshal(buf, &kd)
if err != nil {
return KubeArmorProbeData{}, fmt.Errorf("error occured while parsing data from kubeArmor pod %s", err.Error())
}
return kd, nil
}
func getPostureData(probeData []KubeArmorProbeData) map[string]string {
postureData := make(map[string]string)
if len(probeData) > 0 {
postureData["filePosture"] = probeData[0].ContainerDefaultPosture.FileAction
postureData["capabilitiesPosture"] = probeData[0].ContainerDefaultPosture.CapabilitiesAction
postureData["networkPosture"] = probeData[0].ContainerDefaultPosture.NetworkAction
postureData["visibility"] = probeData[0].HostVisibility
}
return postureData
}
func getAnnotatedPodLabels(m map[string]string) mapset.Set[string] {
var a []string
for key, value := range m {
a = append(a, key+":"+value)
}
b := sliceToSet(a)
return b
}
func getNsSecurityPostureAndVisibility(c *k8s.Client, postureData map[string]string) (map[string]*NamespaceData, error) {
// Namespace/host security posture and visibility setting
mp := make(map[string]*NamespaceData)
namespaces, err := c.K8sClientset.CoreV1().Namespaces().List(context.Background(), metav1.ListOptions{})
if err != nil {
return mp, err
}
for _, ns := range namespaces.Items {
filePosture := postureData["filePosture"]
capabilityPosture := postureData["capabilitiesPosture"]
networkPosture := postureData["networkPosture"]
visibility := postureData["visibility"]
if len(ns.Annotations["kubearmor-file-posture"]) > 0 {
filePosture = ns.Annotations["kubearmor-file-posture"]
}
if len(ns.Annotations["kubearmor-capabilities-posture"]) > 0 {
capabilityPosture = ns.Annotations["kubearmor-capabilities-posture"]
}
if len(ns.Annotations["kubearmor-network-posture"]) > 0 {
networkPosture = ns.Annotations["kubearmor-network-posture"]
}
if len(ns.Annotations["kubearmor-visibility"]) > 0 {
visibility = ns.Annotations["kubearmor-visibility"]
}
mp[ns.Name] = &NamespaceData{
NsDefaultPosture: tp.DefaultPosture{FileAction: filePosture, CapabilitiesAction: capabilityPosture, NetworkAction: networkPosture},
NsVisibilityString: visibility,
NsVisibility: Visibility{
Process: strings.Contains(visibility, "process"),
File: strings.Contains(visibility, "file"),
Network: strings.Contains(visibility, "network"),
Capabilities: strings.Contains(visibility, "capabilities"),
},
NsPostureString: "File(" + filePosture + "), Capabilities(" + capabilityPosture + "), Network (" + networkPosture + ")",
}
}
return mp, err
}
func getAnnotatedPods(c *k8s.Client, o Options, postureData map[string]string) (map[string]interface{}, [][]string, error) {
// Annotated Pods Description
var data [][]string
pods, err := c.K8sClientset.CoreV1().Pods("").List(context.Background(), metav1.ListOptions{})
if err != nil {
return nil, [][]string{}, err
}
armoredPodData := make(map[string]*NamespaceData)
mp, err := getNsSecurityPostureAndVisibility(c, postureData)
if err != nil {
return nil, [][]string{}, err
}
policyMap, err := getPoliciesOnAnnotatedPods(c)
if err != nil {
o.printToOutput(red, " Error getting policies on annotated pods")
}
for _, p := range pods.Items {
if p.Annotations["kubearmor-policy"] == "enabled" {
armoredPod, err := c.K8sClientset.CoreV1().Pods(p.Namespace).Get(context.Background(), p.Name, metav1.GetOptions{})
if err != nil {
return nil, [][]string{}, err
}
if _, exists := mp[armoredPod.Namespace]; !exists {
data = append(data, []string{armoredPod.Namespace, "", "", armoredPod.Name, ""})
} else {
data = append(data, []string{armoredPod.Namespace, mp[armoredPod.Namespace].NsPostureString, mp[armoredPod.Namespace].NsVisibilityString, armoredPod.Name, ""})
}
labels := getAnnotatedPodLabels(armoredPod.Labels)
for policyKey, policyValue := range policyMap {
s2 := sliceToSet(policyValue)
if s2.IsSubset(labels) {
if !checkIfDataAlreadyContainsPodName(data, armoredPod.Name, policyKey) {
data = append(data, []string{armoredPod.Namespace, mp[armoredPod.Namespace].NsPostureString, mp[armoredPod.Namespace].NsVisibilityString, armoredPod.Name, policyKey})
}
}
}
}
}
// sorting according to namespaces, for merging of cells with same namespaces
sort.SliceStable(data, func(i, j int) bool {
return data[i][0] < data[j][0]
})
for _, v := range data {
if _, exists := armoredPodData[v[0]]; !exists {
armoredPodData[v[0]] = &NamespaceData{
NsDefaultPosture: mp[v[0]].NsDefaultPosture,
NsVisibility: mp[v[0]].NsVisibility,
}
}
armoredPodData[v[0]].NsPodList = append(armoredPodData[v[0]].NsPodList, PodInfo{PodName: v[3], Policy: v[4]})
}
return map[string]interface{}{"Namespaces": armoredPodData}, data, nil
}
func getPoliciesOnAnnotatedPods(c *k8s.Client) (map[string][]string, error) {
maps := make(map[string][]string)
kspInterface := c.KSPClientset.KubeArmorPolicies("")
policies, err := kspInterface.List(context.Background(), metav1.ListOptions{})
if err != nil {
return nil, err
}
if len(policies.Items) > 0 {
for _, policy := range policies.Items {
selectLabels := policy.Spec.Selector.MatchLabels
for key, value := range selectLabels {
maps[policy.Name] = append(maps[policy.Name], key+":"+value)
}
}
}
return maps, nil
}
func checkIfDataAlreadyContainsPodName(input [][]string, name string, policy string) bool {
for _, slice := range input {
//if slice contains podname, then append the policy to the existing policies
if slices.Contains(slice, name) {
if slice[4] == "" {
slice[4] = policy
} else {
slice[4] = slice[4] + "\n" + policy
}
return true
}
}
return false
}
func sliceToSet(mySlice []string) mapset.Set[string] {
mySet := mapset.NewSet[string]()
for _, ele := range mySlice {
mySet.Add(ele)
}
return mySet
}