diff --git a/cautils/customerloader.go b/cautils/customerloader.go index 359264b5..981d9d0c 100644 --- a/cautils/customerloader.go +++ b/cautils/customerloader.go @@ -3,8 +3,10 @@ package cautils import ( "context" "encoding/json" + "fmt" "io/ioutil" "net/url" + "os" "github.com/armosec/kubescape/cautils/getter" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -31,9 +33,9 @@ func (co *ConfigObj) Json() []byte { } type IClusterConfig interface { - SetCustomerGUID() - GetCustomerGUID() - GenerateURL() string + SetCustomerGUID() error + GetCustomerGUID() string + GenerateURL() } type ClusterConfig struct { @@ -43,18 +45,37 @@ type ClusterConfig struct { configObj *ConfigObj } +type EmptyConfig struct { +} + +func (c *EmptyConfig) GenerateURL() { +} + +func (c *EmptyConfig) SetCustomerGUID() error { + return nil +} + +func (c *EmptyConfig) GetCustomerGUID() string { + return "" +} + +func NewEmptyConfig() *EmptyConfig { + return &EmptyConfig{} +} + func NewClusterConfig(k8s *k8sinterface.KubernetesApi, armoAPI *getter.ArmoAPI) *ClusterConfig { return &ClusterConfig{ k8s: k8s, armoAPI: armoAPI, - defaultNS: "default", // TODO - load default namespace from k8s api + defaultNS: k8sinterface.GetDefaultNamespace(), } } + func (c *ClusterConfig) update(configObj *ConfigObj) { c.configObj = configObj ioutil.WriteFile(getter.GetDefaultPath(configFileName+".json"), c.configObj.Json(), 0664) } -func (c *ClusterConfig) GenerateURL() string { +func (c *ClusterConfig) GenerateURL() { u := url.URL{} u.Scheme = "https" u.Host = getter.ArmoFEURL @@ -64,8 +85,9 @@ func (c *ClusterConfig) GenerateURL() string { q.Add("customerGUID", c.configObj.CustomerGUID) u.RawQuery = q.Encode() + fmt.Println("To view all controls and get remediations visit:") + InfoTextDisplay(os.Stdout, u.String()+"\n") - return u.String() } func (c *ClusterConfig) GetCustomerGUID() string { diff --git a/cautils/k8sinterface/k8sconfig.go b/cautils/k8sinterface/k8sconfig.go index dae322de..a0ca53eb 100644 --- a/cautils/k8sinterface/k8sconfig.go +++ b/cautils/k8sinterface/k8sconfig.go @@ -6,20 +6,20 @@ import ( "os" "strings" - "k8s.io/client-go/tools/clientcmd" - "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" restclient "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" // DO NOT REMOVE - load cloud providers auth _ "k8s.io/client-go/plugin/pkg/client/auth" "sigs.k8s.io/controller-runtime/pkg/client/config" ) +var ConnectedToCluster = true + // K8SConfig pointer to k8s config var K8SConfig *restclient.Config -var K8SCmdConfig clientcmd.ClientConfig // KubernetesApi - type KubernetesApi struct { @@ -30,8 +30,15 @@ type KubernetesApi struct { // NewKubernetesApi - func NewKubernetesApi() *KubernetesApi { + var kubernetesClient *kubernetes.Clientset + var err error - kubernetesClient, err := kubernetes.NewForConfig(GetK8sConfig()) + if !IsConnectedToCluster() { + fmt.Println(fmt.Errorf("failed to load kubernetes config: no configuration has been provided, try setting KUBECONFIG environment variable")) + os.Exit(1) + } + + kubernetesClient, err = kubernetes.NewForConfig(GetK8sConfig()) if err != nil { fmt.Printf("Failed to load config file, reason: %s", err.Error()) os.Exit(1) @@ -54,7 +61,6 @@ var RunningIncluster bool // LoadK8sConfig load config from local file or from cluster func LoadK8sConfig() error { - kubeconfig, err := config.GetConfig() if err != nil { return fmt.Errorf("failed to load kubernetes config: %s", strings.ReplaceAll(err.Error(), "KUBERNETES_MASTER", "KUBECONFIG")) @@ -69,11 +75,42 @@ func LoadK8sConfig() error { // GetK8sConfig get config. load if not loaded yet func GetK8sConfig() *restclient.Config { - if K8SConfig == nil { - if err := LoadK8sConfig(); err != nil { - fmt.Println(err.Error()) - os.Exit(1) - } + if !IsConnectedToCluster() { + return nil } return K8SConfig } + +func IsConnectedToCluster() bool { + if K8SConfig == nil { + if err := LoadK8sConfig(); err != nil { + ConnectedToCluster = false + } + } + return ConnectedToCluster +} +func GetClusterName() string { + if !ConnectedToCluster { + return "" + } + + kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(clientcmd.NewDefaultClientConfigLoadingRules(), &clientcmd.ConfigOverrides{}) + config, err := kubeConfig.RawConfig() + if err != nil { + return "" + } + // TODO - Handle if empty + return config.CurrentContext +} + +func GetDefaultNamespace() string { + clientCfg, err := clientcmd.NewDefaultClientConfigLoadingRules().Load() + if err != nil { + return "default" + } + namespace := clientCfg.Contexts[clientCfg.CurrentContext].Namespace + if namespace == "" { + namespace = "default" + } + return namespace +} diff --git a/cautils/scaninfo.go b/cautils/scaninfo.go index 8e4a5deb..2bb23564 100644 --- a/cautils/scaninfo.go +++ b/cautils/scaninfo.go @@ -19,6 +19,7 @@ type ScanInfo struct { InputPatterns []string Silent bool FailThreshold uint16 + DoNotSendResults bool } type Getters struct { @@ -80,3 +81,8 @@ func (scanInfo *ScanInfo) setOutputFile() { func (scanInfo *ScanInfo) ScanRunningCluster() bool { return len(scanInfo.InputPatterns) == 0 } + +// func (scanInfo *ScanInfo) ConnectedToCluster(k8s k8sinterface.) bool { +// _, err := k8s.KubernetesClient.CoreV1().Pods("").List(context.TODO(), metav1.ListOptions{}) +// return err == nil +// } diff --git a/cmd/framework.go b/cmd/framework.go index 7e0c1111..50bad405 100644 --- a/cmd/framework.go +++ b/cmd/framework.go @@ -5,7 +5,6 @@ import ( "fmt" "io" "io/ioutil" - "math/rand" "os" "strings" @@ -95,7 +94,7 @@ func init() { frameworkCmd.Flags().StringVarP(&scanInfo.Output, "output", "o", "", "Output file. print output to file and not stdout") frameworkCmd.Flags().BoolVarP(&scanInfo.Silent, "silent", "s", false, "Silent progress messages") frameworkCmd.Flags().Uint16VarP(&scanInfo.FailThreshold, "fail-threshold", "t", 0, "Failure threshold is the percent bellow which the command fails and returns exit code -1") - + frameworkCmd.Flags().BoolVarP(&scanInfo.DoNotSendResults, "results-locally", "", false, "Kubescape sends scan results to its backend to allow users to control exceptions and maintain chronological scan results. Use –results-locally if you do not wish to use these features") } func CliSetup() error { @@ -107,7 +106,9 @@ func CliSetup() error { } var k8s *k8sinterface.KubernetesApi - if scanInfo.ScanRunningCluster() { + if !scanInfo.ScanRunningCluster() { + k8sinterface.ConnectedToCluster = false + } else { k8s = k8sinterface.NewKubernetesApi() } @@ -118,12 +119,18 @@ func CliSetup() error { policyHandler := policyhandler.NewPolicyHandler(&processNotification, k8s) // load cluster config - clusterConfig := cautils.NewClusterConfig(k8s, getter.NewArmoAPI()) + var clusterConfig cautils.IClusterConfig + if !scanInfo.DoNotSendResults && k8sinterface.ConnectedToCluster { + clusterConfig = cautils.NewClusterConfig(k8s, getter.NewArmoAPI()) + } else { + clusterConfig = cautils.NewEmptyConfig() + } + if err := clusterConfig.SetCustomerGUID(); err != nil { fmt.Println(err) } cautils.CustomerGUID = clusterConfig.GetCustomerGUID() - cautils.ClusterName = generateClusterName() + cautils.ClusterName = k8sinterface.GetClusterName() // cli handler setup go func() { @@ -144,7 +151,7 @@ func CliSetup() error { score := resultsHandling.HandleResults() // print report url - fmt.Println(clusterConfig.GenerateURL()) + clusterConfig.GenerateURL() adjustedFailThreshold := float32(scanInfo.FailThreshold) / 100 if score < adjustedFailThreshold { @@ -181,21 +188,3 @@ func (clihandler *CLIHandler) Scan() error { } return nil } - -func generateClusterName() string { - name := fmt.Sprintf("%d", rand.Int()) - if k8sinterface.K8SConfig == nil { - return name - } - if k8sinterface.K8SConfig.Host != "" { - name = k8sinterface.K8SConfig.Host - } else if k8sinterface.K8SConfig.ServerName != "" { - name = k8sinterface.K8SConfig.ServerName - } - - name = strings.ReplaceAll(name, ".", "-") - name = strings.ReplaceAll(name, " ", "-") - name = strings.ReplaceAll(name, "https://", "") - name = strings.ReplaceAll(name, ":", "-") - return name -} diff --git a/opaprocessor/processorhandler.go b/opaprocessor/processorhandler.go index 565c7cac..1003bf91 100644 --- a/opaprocessor/processorhandler.go +++ b/opaprocessor/processorhandler.go @@ -221,6 +221,10 @@ func (opap *OPAProcessor) regoEval(inputObj []map[string]interface{}, compiledRe func (opap *OPAProcessor) updateScore() { + if !k8sinterface.ConnectedToCluster { + return + } + // calculate score s := score.NewScore(k8sinterface.NewKubernetesApi(), ScoreConfigPath) s.Calculate(opap.PostureReport.FrameworkReports) diff --git a/policyhandler/handlenotification.go b/policyhandler/handlenotification.go index 7083911a..42a02c31 100644 --- a/policyhandler/handlenotification.go +++ b/policyhandler/handlenotification.go @@ -79,7 +79,7 @@ func (policyHandler *PolicyHandler) getPolicies(notification *opapolicy.PolicyNo func (policyHandler *PolicyHandler) getResources(notification *opapolicy.PolicyNotification, opaSessionObj *cautils.OPASessionObj, scanInfo *cautils.ScanInfo) (*cautils.K8SResources, error) { var k8sResources *cautils.K8SResources var err error - if scanInfo.ScanRunningCluster() { + if k8sinterface.ConnectedToCluster { k8sResources, err = policyHandler.getK8sResources(opaSessionObj.Frameworks, ¬ification.Designators, scanInfo.ExcludedNamespaces) } else { k8sResources, err = policyHandler.loadResources(opaSessionObj.Frameworks, scanInfo) diff --git a/resultshandling/printer/printresults.go b/resultshandling/printer/printresults.go index 0e43ad86..42007240 100644 --- a/resultshandling/printer/printresults.go +++ b/resultshandling/printer/printresults.go @@ -178,6 +178,10 @@ func (printer *Printer) printResult(controlName string, controlSummary *ControlS } +func (printer *Printer) PrintUrl(url string) { + cautils.InfoTextDisplay(printer.writer, url) +} + func generateRow(control string, cs ControlSummary) []string { row := []string{control} row = append(row, cs.ToSlice()...) diff --git a/resultshandling/reporter/reporteventreceiver.go b/resultshandling/reporter/reporteventreceiver.go index efe57f27..0e346c12 100644 --- a/resultshandling/reporter/reporteventreceiver.go +++ b/resultshandling/reporter/reporteventreceiver.go @@ -28,6 +28,7 @@ func (report *ReportEventReceiver) ActionSendReportListenner(opaSessionObj *caut if cautils.CustomerGUID == "" { return } + //Add score opaSessionObj.PostureReport.RemoveData() if err := report.Send(opaSessionObj.PostureReport); err != nil { fmt.Println(err)