From 41fcb80acecee569eae2cfdfcdee5cf7aec1da54 Mon Sep 17 00:00:00 2001 From: Matthias Bertschy Date: Fri, 30 Aug 2024 08:53:48 +0200 Subject: [PATCH] add guard time when scanning naked pods Signed-off-by: Matthias Bertschy --- config/config.go | 9 +++++++++ mainhandler/handlerequests.go | 16 +++++++++++++++- watcher/podwatcher.go | 30 +++++++++++++++++++++++++----- 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/config/config.go b/config/config.go index 2a416ca..02f7b09 100644 --- a/config/config.go +++ b/config/config.go @@ -102,6 +102,8 @@ type Config struct { HTTPExporterConfig *exporters.HTTPExporterConfig `mapstructure:"httpExporterConfig"` ExcludeNamespaces []string `mapstructure:"excludeNamespaces"` IncludeNamespaces []string `mapstructure:"includeNamespaces"` + // PodScanGuardTime is the time to wait before scanning a pod without a parent + PodScanGuardTime time.Duration `mapstructure:"podScanGuardTime"` } // IConfig is an interface for all config types used in the operator @@ -121,6 +123,7 @@ type IConfig interface { KubescapeURL() string KubevulnURL() string SkipNamespace(ns string) bool + GuardTime() time.Duration } // OperatorConfig implements IConfig @@ -185,6 +188,7 @@ func (c *OperatorConfig) MatchingRulesFilename() string { func (c *OperatorConfig) GatewayWebsocketURL() string { return c.clusterConfig.GatewayWebsocketURL } + func (c *OperatorConfig) ConcurrencyWorkers() int { return c.serviceConfig.ConcurrencyWorkers } @@ -224,6 +228,10 @@ func (c *OperatorConfig) EventReceiverURL() string { return c.eventReceiverRestURL } +func (c *OperatorConfig) GuardTime() time.Duration { + return c.serviceConfig.PodScanGuardTime +} + func LoadConfig(path string) (Config, error) { viper.AddConfigPath(path) viper.SetConfigName("config") @@ -236,6 +244,7 @@ func LoadConfig(path string) (Config, error) { viper.SetDefault("triggerSecurityFramework", false) viper.SetDefault("matchingRulesFilename", "/etc/config/matchingRules.json") viper.SetDefault("eventDeduplicationInterval", 2*time.Minute) + viper.SetDefault("podScanGuardTime", 3*time.Hour) viper.AutomaticEnv() diff --git a/mainhandler/handlerequests.go b/mainhandler/handlerequests.go index 9a249e3..7f38649 100644 --- a/mainhandler/handlerequests.go +++ b/mainhandler/handlerequests.go @@ -8,6 +8,8 @@ import ( "time" "github.com/kubescape/backend/pkg/versioncheck" + "github.com/kubescape/k8s-interface/workloadinterface" + "k8s.io/apimachinery/pkg/runtime" core1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -384,12 +386,24 @@ func (mainHandler *MainHandler) HandleImageScanningScopedRequest(ctx context.Con pod.Kind = "Pod" // get pod instanceIDs - instanceIDs, err := instanceidhandlerv1.GenerateInstanceIDFromPod(&pod) + unstructuredObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&pod) + if err != nil { + logger.L().Ctx(ctx).Error("failed to convert pod to unstructured", helpers.String("pod", pod.GetName()), helpers.String("namespace", pod.GetNamespace()), helpers.Error(err)) + continue + } + wl := workloadinterface.NewWorkloadObj(unstructuredObj) + instanceIDs, err := instanceidhandlerv1.GenerateInstanceID(wl) if err != nil { logger.L().Ctx(ctx).Error("failed to generate instance ID for pod", helpers.String("pod", pod.GetName()), helpers.String("namespace", pod.GetNamespace()), helpers.Error(err)) continue } + // for naked pods, only handle if pod is older than guard time + if !k8sinterface.WorkloadHasParent(wl) && time.Now().Before(pod.CreationTimestamp.Add(mainHandler.config.GuardTime())) { + logger.L().Debug("naked pod younger than guard time detected, skipping scan", helpers.String("pod", pod.GetName()), helpers.String("namespace", pod.GetNamespace())) + continue + } + for _, instanceID := range instanceIDs { s, _ := instanceID.GetSlug(false) if ok := slugs[s]; ok { diff --git a/watcher/podwatcher.go b/watcher/podwatcher.go index 8d94dbc..51790e5 100644 --- a/watcher/podwatcher.go +++ b/watcher/podwatcher.go @@ -3,8 +3,10 @@ package watcher import ( "context" "errors" + "time" mapset "github.com/deckarep/golang-set/v2" + "github.com/kubescape/k8s-interface/workloadinterface" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/tools/pager" @@ -68,26 +70,44 @@ func (wh *WatchHandler) PodWatch(ctx context.Context, workerPool *ants.PoolWithF if !ok { continue } + // handle pod events switch event.Type { case watch.Modified, watch.Added: - wh.handlePodWatcher(ctx, pod, workerPool) - case watch.Deleted: + unstructuredObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(pod) + if err != nil { + logger.L().Ctx(ctx).Error("failed to convert pod to unstructured", helpers.String("pod", pod.GetName()), helpers.String("namespace", pod.GetNamespace()), helpers.Error(err)) + continue + } + wl := workloadinterface.NewWorkloadObj(unstructuredObj) + if k8sinterface.WorkloadHasParent(wl) { + wh.handlePodWatcher(ctx, pod, wl, workerPool) + } else { + // for naked pods, only handle if pod still exists after guard time + logger.L().Debug("naked pod detected, delaying scan", helpers.String("pod", pod.GetName()), helpers.String("namespace", pod.GetNamespace())) + time.AfterFunc(wh.cfg.GuardTime(), func() { + // use get to check if pod still exists, and refresh the pod object + pod, err := wh.k8sAPI.KubernetesClient.CoreV1().Pods(pod.Namespace).Get(context.Background(), pod.Name, v1.GetOptions{}) + if err == nil { + wh.handlePodWatcher(ctx, pod, wl, workerPool) + } + }) + } + default: continue } - } return nil } // handlePodWatcher handles the pod watch events -func (wh *WatchHandler) handlePodWatcher(ctx context.Context, pod *core1.Pod, workerPool *ants.PoolWithFunc) { +func (wh *WatchHandler) handlePodWatcher(ctx context.Context, pod *core1.Pod, wl *workloadinterface.Workload, workerPool *ants.PoolWithFunc) { // check if we need to add pod.APIVersion = "v1" pod.Kind = "Pod" // get pod instanceIDs - instanceIDs, err := instanceidhandlerv1.GenerateInstanceIDFromPod(pod) + instanceIDs, err := instanceidhandlerv1.GenerateInstanceID(wl) if err != nil { logger.L().Ctx(ctx).Error("failed to generate instance ID for pod", helpers.String("pod", pod.GetName()), helpers.String("namespace", pod.GetNamespace()), helpers.Error(err)) return