From 05305d858bd84aca84b2000d7a824e189ef8a2cc Mon Sep 17 00:00:00 2001 From: Bezalel Brandwine Date: Sun, 28 Nov 2021 12:35:21 +0200 Subject: [PATCH] host sensor flag + user input asking --- cautils/scaninfo.go | 66 ++++++++++++++++++++++------- clihandler/cmd/scan.go | 4 ++ clihandler/initcli.go | 66 ++++++++++++++++++++++------- hostsensorutils/hostsensordeploy.go | 7 +++ 4 files changed, 112 insertions(+), 31 deletions(-) diff --git a/cautils/scaninfo.go b/cautils/scaninfo.go index 02f667b2..f7354ef2 100644 --- a/cautils/scaninfo.go +++ b/cautils/scaninfo.go @@ -1,31 +1,65 @@ package cautils import ( + "fmt" "path/filepath" "github.com/armosec/kubescape/cautils/getter" "github.com/armosec/opa-utils/reporthandling" ) +type BoolPtrFlag struct { + valPtr *bool +} + +func (bpf *BoolPtrFlag) Type() string { + return "bool" +} + +func (bpf *BoolPtrFlag) String() string { + if bpf.valPtr != nil { + return fmt.Sprintf("%v", *bpf.valPtr) + } + return "" +} +func (bpf *BoolPtrFlag) Get() *bool { + return bpf.valPtr +} + +func (bpf *BoolPtrFlag) SetBool(val bool) { + bpf.valPtr = &val +} + +func (bpf *BoolPtrFlag) Set(val string) error { + switch val { + case "true": + bpf.SetBool(true) + case "false": + bpf.SetBool(false) + } + return nil +} + type ScanInfo struct { Getters PolicyIdentifier []reporthandling.PolicyIdentifier - UseExceptions string // Load file with exceptions configuration - ControlsInputs string // Load file with inputs for controls - UseFrom []string // Load framework from local file (instead of download). Use when running offline - UseDefault bool // Load framework from cached file (instead of download). Use when running offline - Format string // Format results (table, json, junit ...) - Output string // Store results in an output file, Output file name - ExcludedNamespaces string // DEPRECATED? - IncludeNamespaces string // DEPRECATED? - InputPatterns []string // Yaml files input patterns - Silent bool // Silent mode - Do not print progress logs - FailThreshold uint16 // Failure score threshold - Submit bool // Submit results to Armo BE - Local bool // Do not submit results - Account string // account ID - FrameworkScan bool // false if scanning control - ScanAll bool // true if scan all frameworks + UseExceptions string // Load file with exceptions configuration + ControlsInputs string // Load file with inputs for controls + UseFrom []string // Load framework from local file (instead of download). Use when running offline + UseDefault bool // Load framework from cached file (instead of download). Use when running offline + Format string // Format results (table, json, junit ...) + Output string // Store results in an output file, Output file name + ExcludedNamespaces string // used for host sensor namespace + IncludeNamespaces string // DEPRECATED? + InputPatterns []string // Yaml files input patterns + Silent bool // Silent mode - Do not print progress logs + FailThreshold uint16 // Failure score threshold + Submit bool // Submit results to Armo BE + HostSensor BoolPtrFlag // Deploy ARMO K8s host sensor to collect data from certain controls + Local bool // Do not submit results + Account string // account ID + FrameworkScan bool // false if scanning control + ScanAll bool // true if scan all frameworks } type Getters struct { diff --git a/clihandler/cmd/scan.go b/clihandler/cmd/scan.go index c69e5ab9..5144de5c 100644 --- a/clihandler/cmd/scan.go +++ b/clihandler/cmd/scan.go @@ -48,4 +48,8 @@ func init() { scanCmd.PersistentFlags().BoolVar(&scanInfo.UseDefault, "use-default", false, "Load local policy object from default path. If not used will download latest") scanCmd.PersistentFlags().StringVar(&scanInfo.UseExceptions, "exceptions", "", "Path to an exceptions obj. If not set will download exceptions from ARMO management portal") scanCmd.PersistentFlags().StringVar(&scanInfo.ControlsInputs, "controls-config", "", "Path to an controls-config obj. If not set will download controls-config from ARMO management portal") + + hostF := scanCmd.PersistentFlags().VarPF(&scanInfo.HostSensor, "enable-host-scan", "", "Deploy ARMO K8s host-sensor daemonset in the scanned cluster. Deleting it right after we collecting the data. Required to collect valueable data from cluster nodes for certain controls") + hostF.NoOptDefVal = "true" + hostF.DefValue = "false, for no TTY in stdin" } diff --git a/clihandler/initcli.go b/clihandler/initcli.go index 1acc8947..187e6ef0 100644 --- a/clihandler/initcli.go +++ b/clihandler/initcli.go @@ -2,6 +2,7 @@ package clihandler import ( "fmt" + "io/fs" "os" "github.com/armosec/armoapi-go/armotypes" @@ -27,6 +28,38 @@ type componentInterfaces struct { printerHandler printer.IPrinter } +func initHostSensor(scanInfo *cautils.ScanInfo, k8s *k8sinterface.KubernetesApi) { + + hasHostSensorControls := true + // we need to determined which controls needs host sensor + if scanInfo.HostSensor.Get() == nil && hasHostSensorControls { + scanInfo.HostSensor.SetBool(askUserForHostSensor()) + } + if hostSensorVal := scanInfo.HostSensor.Get(); hostSensorVal != nil && *hostSensorVal { + hostSensorHandler, err := hostsensorutils.NewHostSensorHandler(k8s) + if hostSensorHandler != nil { + defer func(hostSensorHandler *hostsensorutils.HostSensorHandler) { + if err := hostSensorHandler.TearDown(); err != nil { + glog.Errorf("failed to tear down host sensor: %v", err) + } + }(hostSensorHandler) + } + if err != nil { + glog.Errorf("failed to deploy host sensor: %v", err) + return + } + scanInfo.ExcludedNamespaces = fmt.Sprintf("%s,%s", scanInfo.ExcludedNamespaces, hostSensorHandler.DaemonSet.Namespace) + data, err := hostSensorHandler.GetKubeletConfigurations() + if err != nil { + glog.Errorf("failed to get kubelet configuration from host sensor: %v", err) + } else { + glog.Infof("kubelet configurations from host sensor: %v", data) + } + } else { + fmt.Printf("Skipping nodes scanning\n") + } +} + func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces { var resourceHandler resourcehandler.IResourceHandler var clusterConfig cautils.IClusterConfig @@ -45,21 +78,7 @@ func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces { scanningTarget = "yaml" } else { k8s := k8sinterface.NewKubernetesApi() - hostSensorHandler, err := hostsensorutils.NewHostSensorHandler(k8s) - if err != nil { - glog.Errorf("failed to deploy host sensor: %v", err) - } - data, err := hostSensorHandler.GetKubeletConfigurations() - if err != nil { - glog.Errorf("failed to get kubelet configuration from host sensor: %v", err) - } else { - glog.Infof("kubelet configurations from host sensor: %v", data) - } - if hostSensorHandler != nil { - if err := hostSensorHandler.TearDown(); err != nil { - glog.Errorf("failed to tear down host sensor: %v", err) - } - } + initHostSensor(scanInfo, k8s) resourceHandler = resourcehandler.NewK8sResourceHandler(k8s, getFieldSelector(scanInfo)) clusterConfig = cautils.ClusterConfigSetup(scanInfo, k8s, getter.GetArmoAPIConnector()) @@ -189,3 +208,20 @@ func Submit(submitInterfaces cliinterfaces.SubmitInterfaces) error { return nil } + +func askUserForHostSensor() bool { + if ssss, err := os.Stdin.Stat(); err == nil { + // fmt.Printf("Found stdin type: %s\n", ssss.Mode().Type()) + if ssss.Mode().Type()&(fs.ModeDevice|fs.ModeCharDevice) > 0 { //has TTY + fmt.Printf("Would you like to scan K8s nodes? [y/N]. This is required to collect valuable data for certain controls\n") + fmt.Printf("Use --enable-host-scan flag to suppress this message\n") + var b []byte = make([]byte, 1) + if n, err := os.Stdin.Read(b); err == nil { + if n > 0 && len(b) > 0 && (b[0] == 'y' || b[0] == 'Y') { + return true + } + } + } + } + return false +} diff --git a/hostsensorutils/hostsensordeploy.go b/hostsensorutils/hostsensordeploy.go index 5e6797ed..d94b93d0 100644 --- a/hostsensorutils/hostsensordeploy.go +++ b/hostsensorutils/hostsensordeploy.go @@ -37,6 +37,13 @@ func NewHostSensorHandler(k8sObj *k8sinterface.KubernetesApi) (*HostSensorHandle if k8sObj == nil { return nil, fmt.Errorf("nil k8s interface received") } + // Don't deploy on cluster with no nodes. Some cloud providers prevents termination of K8s objects for cluster with no nodes!!! + if nodeList, err := k8sObj.KubernetesClient.NodeV1().RuntimeClasses().List(k8sObj.Context, metav1.ListOptions{}); err != nil || len(nodeList.Items) == 0 { + if err == nil { + err = fmt.Errorf("no nodes to scan") + } + return nil, fmt.Errorf("in NewHostSensorHandler, failed to get nodes list: %v", err) + } hsh := &HostSensorHandler{ k8sObj: k8sObj, HostSensorPodNames: map[string]string{},