diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 480a3f5d..081f3939 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -29,7 +29,6 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] steps: - uses: actions/checkout@v1 - - name: Set up Go uses: actions/setup-go@v2 with: @@ -62,6 +61,7 @@ jobs: build-docker: name: Build docker container, tag and upload to registry needs: build + if: ${{ github.repository == 'armosec/kubescape' }} runs-on: ubuntu-latest steps: diff --git a/.github/workflows/build_dev.yaml b/.github/workflows/build_dev.yaml index 6ccac1a5..9bbd45a5 100644 --- a/.github/workflows/build_dev.yaml +++ b/.github/workflows/build_dev.yaml @@ -40,6 +40,7 @@ jobs: build-docker: name: Build docker container, tag and upload to registry needs: build + if: ${{ github.repository == 'armosec/kubescape' }} runs-on: ubuntu-latest steps: diff --git a/.gitignore b/.gitignore index 24f45ac4..bf33394d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.vs* *kubescape* *debug* +*vender* .idea \ No newline at end of file diff --git a/README.md b/README.md index 2b82689a..2e52fd04 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,12 @@ [![build](https://github.com/armosec/kubescape/actions/workflows/build.yaml/badge.svg)](https://github.com/armosec/kubescape/actions/workflows/build.yaml) [![Go Report Card](https://goreportcard.com/badge/github.com/armosec/kubescape)](https://goreportcard.com/report/github.com/armosec/kubescape) -Kubescape is the first tool for testing if Kubernetes is deployed securely as defined in [Kubernetes Hardening Guidance by NSA and CISA](https://www.nsa.gov/Press-Room/News-Highlights/Article/Article/2716980/nsa-cisa-release-kubernetes-hardening-guidance/) +Kubescape is the first open-source tool for testing if Kubernetes is deployed securely according to multiple frameworks: +regulatory, customized company policies and DevSecOps best practices, such as the [NSA-CISA](https://www.armosec.io/blog/kubernetes-hardening-guidance-summary-by-armo) and the [MITRE ATT&CK®](https://www.microsoft.com/security/blog/2021/03/23/secure-containerized-environments-with-updated-threat-matrix-for-kubernetes/) . +Kubescape scans K8s clusters, YAML files, and HELM charts, and detect misconfigurations and software vulnerabilities at early stages of the CI/CD pipeline and provides a risk score instantly and risk trends over time. +Kubescape integrates natively with other DevOps tools, including Jenkins, CircleCI and Github workflows. -Use Kubescape to test clusters or scan single YAML files and integrate it to your processes. +
@@ -21,11 +24,9 @@ curl -s https://raw.githubusercontent.com/armosec/kubescape/master/install.sh | ## Run: ``` -kubescape scan framework nsa --exclude-namespaces kube-system,kube-public +kubescape scan framework nsa ``` -If you wish to scan all namespaces in your cluster, remove the `--exclude-namespaces` flag. - ### Click [👍](https://github.com/armosec/kubescape/stargazers) if you want us to continue to develop and improve Kubescape 😀 @@ -86,15 +87,15 @@ Set-ExecutionPolicy RemoteSigned -scope CurrentUser ### Examples -* Scan a running Kubernetes cluster with [`nsa`](https://www.nsa.gov/News-Features/Feature-Stories/Article-View/Article/2716980/nsa-cisa-release-kubernetes-hardening-guidance/) framework and submit results to [Armo portal](https://portal.armo.cloud/) +* Scan a running Kubernetes cluster with [`nsa`](https://www.nsa.gov/News-Features/Feature-Stories/Article-View/Article/2716980/nsa-cisa-release-kubernetes-hardening-guidance/) framework and submit results to [ARMO portal](https://portal.armo.cloud/) ``` -kubescape scan framework nsa --exclude-namespaces kube-system,kube-public --submit +kubescape scan framework nsa --submit ``` -* Scan a running Kubernetes cluster with [`mitre`](https://www.microsoft.com/security/blog/2020/04/02/attack-matrix-kubernetes/) framework and submit results to [Armo portal](https://portal.armo.cloud/) +* Scan a running Kubernetes cluster with [`MITRE ATT&CK®`](https://www.microsoft.com/security/blog/2021/03/23/secure-containerized-environments-with-updated-threat-matrix-for-kubernetes/) framework and submit results to [ARMO portal](https://portal.armo.cloud/) ``` -kubescape scan framework mitre --exclude-namespaces kube-system,kube-public --submit +kubescape scan framework mitre --submit ``` * Scan local `yaml`/`json` files before deploying. [Take a look at the demonstration](https://youtu.be/Ox6DaR7_4ZI) @@ -118,7 +119,7 @@ kubescape scan framework nsa --exclude-namespaces kube-system,kube-public --form kubescape scan framework nsa --exclude-namespaces kube-system,kube-public --format junit --output results.xml ``` -* Scan with exceptions, objects with exceptions will be presented as `warning` and not `fail` +* Scan with exceptions, objects with exceptions will be presented as `exclude` and not `fail` ``` kubescape scan framework nsa --exceptions examples/exceptions.json ``` @@ -187,7 +188,7 @@ go build -o kubescape . 3. Run ``` -./kubescape scan framework nsa --exclude-namespaces kube-system,kube-public +./kubescape scan framework nsa ``` 4. Enjoy :zany_face: diff --git a/cautils/customerloader.go b/cautils/customerloader.go index c97acdb1..499e7630 100644 --- a/cautils/customerloader.go +++ b/cautils/customerloader.go @@ -30,6 +30,7 @@ type ConfigObj struct { CustomerGUID string `json:"customerGUID"` Token string `json:"invitationParam"` CustomerAdminEMail string `json:"adminMail"` + ClusterName string `json:"clusterName"` } func (co *ConfigObj) Json() []byte { @@ -39,14 +40,30 @@ func (co *ConfigObj) Json() []byte { return []byte{} } +// Config - convert ConfigObj to config file +func (co *ConfigObj) Config() []byte { + clusterName := co.ClusterName + co.ClusterName = "" // remove cluster name before saving to file + b, err := json.Marshal(co) + co.ClusterName = clusterName + + if err == nil { + return b + } + + return []byte{} +} + // ====================================================================================== // =============================== interface ============================================ // ====================================================================================== type IClusterConfig interface { - // setters - SetCustomerGUID(customerGUID string) error + + // set + SetConfig(customerGUID string) error // getters + GetClusterName() string GetCustomerGUID() string GetConfigObj() *ConfigObj GetK8sAPI() *k8sinterface.KubernetesApi @@ -103,16 +120,17 @@ func ClusterConfigSetup(scanInfo *ScanInfo, k8s *k8sinterface.KubernetesApi, beA type EmptyConfig struct { } -func NewEmptyConfig() *EmptyConfig { return &EmptyConfig{} } -func (c *EmptyConfig) GetConfigObj() *ConfigObj { return &ConfigObj{} } -func (c *EmptyConfig) SetCustomerGUID(customerGUID string) error { return nil } -func (c *EmptyConfig) GetCustomerGUID() string { return "" } -func (c *EmptyConfig) GetK8sAPI() *k8sinterface.KubernetesApi { return nil } // TODO: return mock obj -func (c *EmptyConfig) GetDefaultNS() string { return k8sinterface.GetDefaultNamespace() } -func (c *EmptyConfig) GetBackendAPI() getter.IBackend { return nil } // TODO: return mock obj +func NewEmptyConfig() *EmptyConfig { return &EmptyConfig{} } +func (c *EmptyConfig) SetConfig(customerGUID string) error { return nil } +func (c *EmptyConfig) GetConfigObj() *ConfigObj { return &ConfigObj{} } +func (c *EmptyConfig) GetCustomerGUID() string { return "" } +func (c *EmptyConfig) GetK8sAPI() *k8sinterface.KubernetesApi { return nil } // TODO: return mock obj +func (c *EmptyConfig) GetDefaultNS() string { return k8sinterface.GetDefaultNamespace() } +func (c *EmptyConfig) GetBackendAPI() getter.IBackend { return nil } // TODO: return mock obj +func (c *EmptyConfig) GetClusterName() string { return k8sinterface.GetClusterName() } func (c *EmptyConfig) GenerateURL() { - message := fmt.Sprintf("You can see the results in a user-friendly UI, choose your preferred compliance framework, check risk results history and trends, manage exceptions, get remediation recommendations and much more by registering here: https://%s", getter.GetArmoAPIConnector().GetFrontendURL()) - InfoTextDisplay(os.Stdout, message+"\n") + message := fmt.Sprintf("\nCheckout for more cool features: https://%s\n", getter.GetArmoAPIConnector().GetFrontendURL()) + InfoTextDisplay(os.Stdout, fmt.Sprintf("\n%s\n", message)) } // ====================================================================================== @@ -147,7 +165,7 @@ func (c *ClusterConfig) GenerateURL() { if c.configObj == nil { return } - message := fmt.Sprintf("You can see the results in a user-friendly UI, choose your preferred compliance framework, check risk results history and trends, manage exceptions, get remediation recommendations and much more by registering here: %s", u.String()) + message := fmt.Sprintf("\nCheckout for more cool features: https://%s\n", getter.GetArmoAPIConnector().GetFrontendURL()) if c.configObj.CustomerAdminEMail != "" { InfoTextDisplay(os.Stdout, message+"\n") return @@ -169,10 +187,19 @@ func (c *ClusterConfig) GetCustomerGUID() string { return "" } -func (c *ClusterConfig) SetCustomerGUID(customerGUID string) error { +func (c *ClusterConfig) SetConfig(customerGUID string) error { + if c.configObj == nil { + c.configObj = &ConfigObj{} + } + // cluster name + if c.GetClusterName() == "" { + c.setClusterName(k8sinterface.GetClusterName()) + } + + // ARMO customer GUID if customerGUID != "" && c.GetCustomerGUID() != customerGUID { - c.configObj.CustomerGUID = customerGUID // override config customerGUID + c.setCustomerGUID(customerGUID) // override config customerGUID } customerGUID = c.GetCustomerGUID() @@ -181,10 +208,10 @@ func (c *ClusterConfig) SetCustomerGUID(customerGUID string) error { tenantResponse, err := c.backendAPI.GetCustomerGUID(customerGUID) if err == nil && tenantResponse != nil { if tenantResponse.AdminMail != "" { // this customer already belongs to some user - c.configObj.CustomerAdminEMail = tenantResponse.AdminMail + c.setCustomerAdminEMail(tenantResponse.AdminMail) } else { - c.configObj.Token = tenantResponse.Token - c.configObj.CustomerGUID = tenantResponse.TenantID + c.setToken(tenantResponse.Token) + c.setCustomerGUID(tenantResponse.TenantID) } } else { if err != nil && !strings.Contains(err.Error(), "already exists") { @@ -198,15 +225,28 @@ func (c *ClusterConfig) SetCustomerGUID(customerGUID string) error { } else { c.createConfigMap() } - if existsConfigFile() { - c.updateConfigFile() - } else { - c.createConfigFile() - } + c.updateConfigFile() return nil } +func (c *ClusterConfig) setToken(token string) { + c.configObj.Token = token +} + +func (c *ClusterConfig) setCustomerAdminEMail(customerAdminEMail string) { + c.configObj.CustomerAdminEMail = customerAdminEMail +} +func (c *ClusterConfig) setCustomerGUID(customerGUID string) { + c.configObj.CustomerGUID = customerGUID +} + +func (c *ClusterConfig) setClusterName(clusterName string) { + c.configObj.ClusterName = clusterName +} +func (c *ClusterConfig) GetClusterName() string { + return c.configObj.ClusterName +} func (c *ClusterConfig) LoadConfig() { // get from configMap if c.existsConfigMap() { @@ -220,8 +260,9 @@ func (c *ClusterConfig) LoadConfig() { func (c *ClusterConfig) ToMapString() map[string]interface{} { m := map[string]interface{}{} - bc, _ := json.Marshal(c.configObj) - json.Unmarshal(bc, &m) + if bc, err := json.Marshal(c.configObj); err == nil { + json.Unmarshal(bc, &m) + } return m } func (c *ClusterConfig) loadConfigFromConfigMap() (*ConfigObj, error) { @@ -360,14 +401,7 @@ func (c *ClusterConfig) updateConfigMap() error { } func (c *ClusterConfig) updateConfigFile() error { - if err := os.WriteFile(ConfigFileFullPath(), c.configObj.Json(), 0664); err != nil { - return err - } - return nil -} - -func (c *ClusterConfig) createConfigFile() error { - if err := os.WriteFile(ConfigFileFullPath(), c.configObj.Json(), 0664); err != nil { + if err := os.WriteFile(ConfigFileFullPath(), c.configObj.Config(), 0664); err != nil { return err } return nil diff --git a/cautils/downloadinfo.go b/cautils/downloadinfo.go index 7136be43..c54601eb 100644 --- a/cautils/downloadinfo.go +++ b/cautils/downloadinfo.go @@ -3,4 +3,5 @@ package cautils type DownloadInfo struct { Path string FrameworkName string + ControlName string } diff --git a/cautils/getter/downloadreleasedpolicy.go b/cautils/getter/downloadreleasedpolicy.go index 713704e7..944c1bd1 100644 --- a/cautils/getter/downloadreleasedpolicy.go +++ b/cautils/getter/downloadreleasedpolicy.go @@ -1,12 +1,9 @@ package getter import ( - "encoding/json" - "fmt" - "io" - "net/http" - "time" + "strings" + "github.com/armosec/opa-utils/gitregostore" "github.com/armosec/opa-utils/reporthandling" ) @@ -16,71 +13,34 @@ import ( // Download released version type DownloadReleasedPolicy struct { - hostURL string - httpClient *http.Client + gs *gitregostore.GitRegoStore } func NewDownloadReleasedPolicy() *DownloadReleasedPolicy { return &DownloadReleasedPolicy{ - hostURL: "", - httpClient: &http.Client{Timeout: 61 * time.Second}, + gs: gitregostore.InitDefaultGitRegoStore(), } } +// Return control per name/id using ARMO api +func (drp *DownloadReleasedPolicy) GetControl(policyName string) (*reporthandling.Control, error) { + var control *reporthandling.Control + var err error + if strings.HasPrefix(policyName, "C-") || strings.HasPrefix(policyName, "c-") { + control, err = drp.gs.GetOPAControlByID(policyName) + } else { + control, err = drp.gs.GetOPAControlByName(policyName) + } + if err != nil { + return nil, err + } + return control, nil +} + func (drp *DownloadReleasedPolicy) GetFramework(name string) (*reporthandling.Framework, error) { - if err := drp.setURL(name); err != nil { - return nil, err - } - respStr, err := HttpGetter(drp.httpClient, drp.hostURL) + framework, err := drp.gs.GetOPAFrameworkByName(name) if err != nil { return nil, err } - - framework := &reporthandling.Framework{} - if err = JSONDecoder(respStr).Decode(framework); err != nil { - return framework, err - } - - SaveFrameworkInFile(framework, GetDefaultPath(name+".json")) return framework, err } - -func (drp *DownloadReleasedPolicy) setURL(frameworkName string) error { - - latestReleases := "https://api.github.com/repos/armosec/regolibrary/releases/latest" - resp, err := http.Get(latestReleases) - if err != nil { - return fmt.Errorf("failed to get latest releases from '%s', reason: %s", latestReleases, err.Error()) - } - defer resp.Body.Close() - if resp.StatusCode < 200 || 301 < resp.StatusCode { - return fmt.Errorf("failed to download file, status code: %s", resp.Status) - } - - body, err := io.ReadAll(resp.Body) - if err != nil { - return fmt.Errorf("failed to read response body from '%s', reason: %s", latestReleases, err.Error()) - } - var data map[string]interface{} - err = json.Unmarshal(body, &data) - if err != nil { - return fmt.Errorf("failed to unmarshal response body from '%s', reason: %s", latestReleases, err.Error()) - } - - if assets, ok := data["assets"].([]interface{}); ok { - for i := range assets { - if asset, ok := assets[i].(map[string]interface{}); ok { - if name, ok := asset["name"].(string); ok { - if name == frameworkName { - if url, ok := asset["browser_download_url"].(string); ok { - drp.hostURL = url - return nil - } - } - } - } - } - } - return fmt.Errorf("failed to download '%s' - not found", frameworkName) - -} diff --git a/cautils/getter/getpolicies.go b/cautils/getter/getpolicies.go index 3345334c..6d496d78 100644 --- a/cautils/getter/getpolicies.go +++ b/cautils/getter/getpolicies.go @@ -7,6 +7,7 @@ import ( type IPolicyGetter interface { GetFramework(name string) (*reporthandling.Framework, error) + GetControl(policyName string) (*reporthandling.Control, error) } type IExceptionsGetter interface { diff --git a/cautils/getter/getpoliciesutils.go b/cautils/getter/getpoliciesutils.go index af367a3d..7949eb70 100644 --- a/cautils/getter/getpoliciesutils.go +++ b/cautils/getter/getpoliciesutils.go @@ -21,6 +21,31 @@ func GetDefaultPath(name string) string { return defaultfilePath } +// Save control as json in file +func SaveControlInFile(control *reporthandling.Control, pathStr string) error { + encodedData, err := json.Marshal(control) + if err != nil { + return err + } + err = os.WriteFile(pathStr, []byte(fmt.Sprintf("%v", string(encodedData))), 0644) + if err != nil { + if os.IsNotExist(err) { + pathDir := path.Dir(pathStr) + if err := os.Mkdir(pathDir, 0744); err != nil { + return err + } + } else { + return err + + } + err = os.WriteFile(pathStr, []byte(fmt.Sprintf("%v", string(encodedData))), 0644) + if err != nil { + return err + } + } + return nil +} + func SaveFrameworkInFile(framework *reporthandling.Framework, pathStr string) error { encodedData, err := json.Marshal(framework) if err != nil { diff --git a/cautils/getter/loadpolicy.go b/cautils/getter/loadpolicy.go index 47a9bfc2..c976a478 100644 --- a/cautils/getter/loadpolicy.go +++ b/cautils/getter/loadpolicy.go @@ -26,6 +26,22 @@ func NewLoadPolicy(filePath string) *LoadPolicy { } } +// Return control from file +func (lp *LoadPolicy) GetControl(controlName string) (*reporthandling.Control, error) { + + control := &reporthandling.Control{} + f, err := os.ReadFile(lp.filePath) + if err != nil { + return nil, err + } + + err = json.Unmarshal(f, control) + if controlName != "" && !strings.EqualFold(controlName, control.Name) && !strings.EqualFold(controlName, control.ControlID) { + return nil, fmt.Errorf("control from file not matching") + } + return control, err +} + func (lp *LoadPolicy) GetFramework(frameworkName string) (*reporthandling.Framework, error) { framework := &reporthandling.Framework{} diff --git a/cautils/scaninfo.go b/cautils/scaninfo.go index f1838bff..8ca73ca6 100644 --- a/cautils/scaninfo.go +++ b/cautils/scaninfo.go @@ -3,6 +3,7 @@ package cautils import ( "path/filepath" + "github.com/armosec/k8s-interface/k8sinterface" "github.com/armosec/kubescape/cautils/getter" "github.com/armosec/opa-utils/reporthandling" ) @@ -22,6 +23,7 @@ type ScanInfo struct { Submit bool // Submit results to Armo BE Local bool // Do not submit results Account string // account ID + FrameworkScan bool // false if scanning control } type Getters struct { @@ -53,7 +55,6 @@ func (scanInfo *ScanInfo) setUseFrom() { if scanInfo.UseDefault { scanInfo.UseFrom = getter.GetDefaultPath(scanInfo.PolicyIdentifier.Name + ".json") } - } func (scanInfo *ScanInfo) setGetter() { if scanInfo.UseFrom != "" { @@ -84,6 +85,20 @@ func (scanInfo *ScanInfo) ScanRunningCluster() bool { return len(scanInfo.InputPatterns) == 0 } +func (scanInfo *ScanInfo) SetClusterConfig() (IClusterConfig, *k8sinterface.KubernetesApi) { + var clusterConfig IClusterConfig + var k8s *k8sinterface.KubernetesApi + if !scanInfo.ScanRunningCluster() { + k8sinterface.ConnectedToCluster = false + clusterConfig = NewEmptyConfig() + } else { + k8s = k8sinterface.NewKubernetesApi() + // setup cluster config + clusterConfig = ClusterConfigSetup(scanInfo, k8s, getter.GetArmoAPIConnector()) + } + return clusterConfig, k8s +} + // func (scanInfo *ScanInfo) ConnectedToCluster(k8s k8sinterface.) bool { // _, err := k8s.KubernetesClient.CoreV1().Pods("").List(context.TODO(), metav1.ListOptions{}) // return err == nil diff --git a/cmd/cluster.go b/clihandler/cmd/cluster.go similarity index 100% rename from cmd/cluster.go rename to clihandler/cmd/cluster.go diff --git a/cmd/cluster_get.go b/clihandler/cmd/cluster_get.go similarity index 93% rename from cmd/cluster_get.go rename to clihandler/cmd/cluster_get.go index 6203473e..0c0740df 100644 --- a/cmd/cluster_get.go +++ b/clihandler/cmd/cluster_get.go @@ -7,6 +7,7 @@ import ( "github.com/armosec/k8s-interface/k8sinterface" "github.com/armosec/kubescape/cautils" "github.com/armosec/kubescape/cautils/getter" + "github.com/armosec/kubescape/clihandler" "github.com/spf13/cobra" ) @@ -14,7 +15,7 @@ var getCmd = &cobra.Command{ Use: "get ", Short: "Get configuration in cluster", Long: ``, - ValidArgs: supportedFrameworks, + ValidArgs: clihandler.SupportedFrameworks, Args: func(cmd *cobra.Command, args []string) error { if len(args) < 1 || len(args) > 1 { return fmt.Errorf("requires one argument") diff --git a/cmd/cluster_set.go b/clihandler/cmd/cluster_set.go similarity index 100% rename from cmd/cluster_set.go rename to clihandler/cmd/cluster_set.go diff --git a/cmd/config.go b/clihandler/cmd/config.go similarity index 100% rename from cmd/config.go rename to clihandler/cmd/config.go diff --git a/clihandler/cmd/control.go b/clihandler/cmd/control.go new file mode 100644 index 00000000..0618e0c4 --- /dev/null +++ b/clihandler/cmd/control.go @@ -0,0 +1,53 @@ +package cmd + +import ( + "fmt" + "os" + "strings" + + "github.com/armosec/kubescape/cautils" + "github.com/armosec/kubescape/clihandler" + "github.com/armosec/opa-utils/reporthandling" + "github.com/spf13/cobra" +) + +// controlCmd represents the control command +var controlCmd = &cobra.Command{ + Use: "control /", + Short: fmt.Sprintf("The control you wish to use for scan. It must be present in at least one of the folloiwng frameworks: %s", clihandler.ValidFrameworks), + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 && !(cmd.Flags().Lookup("use-from").Changed) { + return fmt.Errorf("requires at least one argument") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + flagValidationControl() + scanInfo.PolicyIdentifier = reporthandling.PolicyIdentifier{} + if !(cmd.Flags().Lookup("use-from").Changed) { + scanInfo.PolicyIdentifier.Name = strings.ToLower(args[0]) + } + scanInfo.FrameworkScan = false + scanInfo.PolicyIdentifier.Kind = reporthandling.KindControl + scanInfo.Init() + cautils.SetSilentMode(scanInfo.Silent) + err := clihandler.CliSetup(scanInfo) + if err != nil { + fmt.Fprintf(os.Stderr, "error: %v\n", err) + os.Exit(1) + } + return nil + }, +} + +func init() { + scanInfo = cautils.ScanInfo{} + scanCmd.AddCommand(controlCmd) +} + +func flagValidationControl() { + if 100 < scanInfo.FailThreshold { + fmt.Println("bad argument: out of range threshold") + os.Exit(1) + } +} diff --git a/clihandler/cmd/download.go b/clihandler/cmd/download.go new file mode 100644 index 00000000..6ed2dadf --- /dev/null +++ b/clihandler/cmd/download.go @@ -0,0 +1,67 @@ +package cmd + +import ( + "fmt" + "strings" + + "github.com/armosec/kubescape/cautils" + "github.com/armosec/kubescape/cautils/getter" + "github.com/armosec/kubescape/clihandler" + "github.com/spf13/cobra" +) + +var downloadInfo cautils.DownloadInfo + +var downloadCmd = &cobra.Command{ + Use: fmt.Sprintf("download framework/control / [flags]\nSupported frameworks: %s", clihandler.ValidFrameworks), + Short: "Download framework/control", + Long: ``, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 2 { + return fmt.Errorf("requires two arguments : framework/control /") + } + if !strings.EqualFold(args[0], "framework") && !strings.EqualFold(args[0], "control") { + return fmt.Errorf("invalid parameter '%s'. Supported parameters: framework, control", args[0]) + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + if strings.EqualFold(args[0], "framework") { + downloadInfo.FrameworkName = strings.ToLower(args[1]) + g := getter.NewDownloadReleasedPolicy() + if downloadInfo.Path == "" { + downloadInfo.Path = getter.GetDefaultPath(downloadInfo.FrameworkName + ".json") + } + frameworks, err := g.GetFramework(downloadInfo.FrameworkName) + if err != nil { + return err + } + err = getter.SaveFrameworkInFile(frameworks, downloadInfo.Path) + if err != nil { + return err + } + } else if strings.EqualFold(args[0], "control") { + downloadInfo.ControlName = strings.ToLower(args[1]) + g := getter.NewDownloadReleasedPolicy() + if downloadInfo.Path == "" { + downloadInfo.Path = getter.GetDefaultPath(downloadInfo.ControlName + ".json") + } + controls, err := g.GetControl(downloadInfo.ControlName) + if err != nil { + return err + } + err = getter.SaveControlInFile(controls, downloadInfo.Path) + if err != nil { + return err + } + } + + return nil + }, +} + +func init() { + rootCmd.AddCommand(downloadCmd) + downloadInfo = cautils.DownloadInfo{} + downloadCmd.Flags().StringVarP(&downloadInfo.Path, "output", "o", "", "Output file. If specified, will store save to `~/.kubescape/.json`") +} diff --git a/clihandler/cmd/framework.go b/clihandler/cmd/framework.go new file mode 100644 index 00000000..bd0bbfe6 --- /dev/null +++ b/clihandler/cmd/framework.go @@ -0,0 +1,90 @@ +package cmd + +import ( + "fmt" + "io" + "os" + "strings" + + "github.com/armosec/kubescape/cautils" + "github.com/armosec/kubescape/clihandler" + "github.com/armosec/opa-utils/reporthandling" + + "github.com/spf13/cobra" +) + +var frameworkCmd = &cobra.Command{ + + Use: fmt.Sprintf("framework [``/`-`] [flags]\nSupported frameworks: %s", clihandler.ValidFrameworks), + Short: fmt.Sprintf("The framework you wish to use. Supported frameworks: %s", strings.Join(clihandler.SupportedFrameworks, ", ")), + Long: "Execute a scan on a running Kubernetes cluster or `yaml`/`json` files (use glob) or `-` for stdin", + ValidArgs: clihandler.SupportedFrameworks, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 && !(cmd.Flags().Lookup("use-from").Changed) { + return fmt.Errorf("requires at least one argument") + } else if len(args) > 0 { + if !isValidFramework(strings.ToLower(args[0])) { + return fmt.Errorf(fmt.Sprintf("supported frameworks: %s", strings.Join(clihandler.SupportedFrameworks, ", "))) + } + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + scanInfo.PolicyIdentifier = reporthandling.PolicyIdentifier{} + scanInfo.PolicyIdentifier.Kind = reporthandling.KindFramework + flagValidationFramework() + if !(cmd.Flags().Lookup("use-from").Changed) { + scanInfo.PolicyIdentifier.Name = strings.ToLower(args[0]) + } + if len(args) > 0 { + if len(args[1:]) == 0 || args[1] != "-" { + scanInfo.InputPatterns = args[1:] + } else { // store stout to file + tempFile, err := os.CreateTemp(".", "tmp-kubescape*.yaml") + if err != nil { + return err + } + defer os.Remove(tempFile.Name()) + + if _, err := io.Copy(tempFile, os.Stdin); err != nil { + return err + } + scanInfo.InputPatterns = []string{tempFile.Name()} + } + } + scanInfo.Init() + cautils.SetSilentMode(scanInfo.Silent) + err := clihandler.CliSetup(scanInfo) + if err != nil { + fmt.Fprintf(os.Stderr, "error: %v\n", err) + os.Exit(1) + } + return nil + }, +} + +func isValidFramework(framework string) bool { + return cautils.StringInSlice(clihandler.SupportedFrameworks, framework) != cautils.ValueNotFound +} + +func init() { + scanCmd.AddCommand(frameworkCmd) + scanInfo = cautils.ScanInfo{} + scanInfo.FrameworkScan = true + frameworkCmd.Flags().BoolVarP(&scanInfo.Submit, "submit", "", false, "Send the scan results to Armo management portal where you can see the results in a user-friendly UI, choose your preferred compliance framework, check risk results history and trends, manage exceptions, get remediation recommendations and much more. By default the results are not submitted") + frameworkCmd.Flags().BoolVarP(&scanInfo.Local, "keep-local", "", false, "If you do not want your Kubescape results reported to Armo backend. Use this flag if you ran with the '--submit' flag in the past and you do not want to submit your current scan results") + frameworkCmd.Flags().StringVarP(&scanInfo.Account, "account", "", "", "Armo portal account ID. Default will load account ID from configMap or config file") + +} + +func flagValidationFramework() { + + if scanInfo.Submit && scanInfo.Local { + fmt.Println("You can use `keep-local` or `submit`, but not both") + os.Exit(1) + } + if 100 < scanInfo.FailThreshold { + fmt.Println("bad argument: out of range threshold") + os.Exit(1) + } +} diff --git a/cmd/local.go b/clihandler/cmd/local.go similarity index 100% rename from cmd/local.go rename to clihandler/cmd/local.go diff --git a/cmd/local_get.go b/clihandler/cmd/local_get.go similarity index 100% rename from cmd/local_get.go rename to clihandler/cmd/local_get.go diff --git a/cmd/local_set.go b/clihandler/cmd/local_set.go similarity index 100% rename from cmd/local_set.go rename to clihandler/cmd/local_set.go diff --git a/cmd/root.go b/clihandler/cmd/root.go similarity index 100% rename from cmd/root.go rename to clihandler/cmd/root.go diff --git a/clihandler/cmd/scan.go b/clihandler/cmd/scan.go new file mode 100644 index 00000000..e86e5a02 --- /dev/null +++ b/clihandler/cmd/scan.go @@ -0,0 +1,41 @@ +package cmd + +import ( + "fmt" + "strings" + + "github.com/armosec/kubescape/cautils" + "github.com/spf13/cobra" +) + +var scanInfo cautils.ScanInfo + +// scanCmd represents the scan command +var scanCmd = &cobra.Command{ + Use: "scan ", + Short: "Scan the current running cluster or yaml files", + Long: `The action you want to perform`, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return fmt.Errorf("requires one argument: framework/control") + } + if !strings.EqualFold(args[0], "framework") && !strings.EqualFold(args[0], "control") { + return fmt.Errorf("invalid parameter '%s'. Supported parameters: framework, control", args[0]) + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + }, +} + +func init() { + rootCmd.AddCommand(scanCmd) + scanCmd.PersistentFlags().StringVarP(&scanInfo.ExcludedNamespaces, "exclude-namespaces", "e", "", "Namespaces to exclude from scanning. Recommended: kube-system, kube-public") + scanCmd.PersistentFlags().StringVarP(&scanInfo.Format, "format", "f", "pretty-printer", `Output format. Supported formats: "pretty-printer"/"json"/"junit"`) + scanCmd.PersistentFlags().StringVarP(&scanInfo.Output, "output", "o", "", "Output file. Print output to file and not stdout") + scanCmd.PersistentFlags().BoolVarP(&scanInfo.Silent, "silent", "s", false, "Silent progress messages") + scanCmd.PersistentFlags().Uint16VarP(&scanInfo.FailThreshold, "fail-threshold", "t", 0, "Failure threshold is the percent bellow which the command fails and returns exit code 1") + scanCmd.PersistentFlags().StringVar(&scanInfo.UseFrom, "use-from", "", "Load local framework object from specified path. If not used will download latest") + scanCmd.PersistentFlags().BoolVar(&scanInfo.UseDefault, "use-default", false, "Load local framework 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") +} diff --git a/cmd/version.go b/clihandler/cmd/version.go similarity index 100% rename from cmd/version.go rename to clihandler/cmd/version.go diff --git a/clihandler/initcli.go b/clihandler/initcli.go new file mode 100644 index 00000000..c335702d --- /dev/null +++ b/clihandler/initcli.go @@ -0,0 +1,100 @@ +package clihandler + +import ( + "fmt" + "os" + "strings" + + "github.com/armosec/armoapi-go/armotypes" + "github.com/armosec/kubescape/cautils" + "github.com/armosec/kubescape/opaprocessor" + "github.com/armosec/kubescape/policyhandler" + "github.com/armosec/kubescape/resultshandling" + "github.com/armosec/kubescape/resultshandling/printer" + "github.com/armosec/kubescape/resultshandling/reporter" + "github.com/armosec/opa-utils/reporthandling" +) + +type CLIHandler struct { + policyHandler *policyhandler.PolicyHandler + scanInfo *cautils.ScanInfo +} + +var SupportedFrameworks = []string{"nsa", "mitre"} +var ValidFrameworks = strings.Join(SupportedFrameworks, ", ") + +func CliSetup(scanInfo cautils.ScanInfo) error { + + clusterConfig, k8s := scanInfo.SetClusterConfig() + + processNotification := make(chan *cautils.OPASessionObj) + reportResults := make(chan *cautils.OPASessionObj) + + // policy handler setup + policyHandler := policyhandler.NewPolicyHandler(&processNotification, k8s) + + if err := clusterConfig.SetConfig(scanInfo.Account); err != nil { + fmt.Println(err) + } + + cautils.ClusterName = clusterConfig.GetClusterName() + cautils.CustomerGUID = clusterConfig.GetCustomerGUID() + + // cli handler setup + go func() { + cli := NewCLIHandler(policyHandler, scanInfo) + if err := cli.Scan(); err != nil { + fmt.Println(err) + os.Exit(1) + } + }() + + // processor setup - rego run + go func() { + opaprocessorObj := opaprocessor.NewOPAProcessorHandler(&processNotification, &reportResults) + opaprocessorObj.ProcessRulesListenner() + }() + + resultsHandling := resultshandling.NewResultsHandler(&reportResults, reporter.NewReportEventReceiver(), printer.NewPrinter(scanInfo.Format, scanInfo.Output)) + score := resultsHandling.HandleResults(scanInfo) + + // print report url + if scanInfo.FrameworkScan { + clusterConfig.GenerateURL() + } + + adjustedFailThreshold := float32(scanInfo.FailThreshold) / 100 + if score < adjustedFailThreshold { + return fmt.Errorf("Scan score is bellow threshold") + } + + return nil +} + +func NewCLIHandler(policyHandler *policyhandler.PolicyHandler, scanInfo cautils.ScanInfo) *CLIHandler { + return &CLIHandler{ + scanInfo: &scanInfo, + policyHandler: policyHandler, + } +} + +func (clihandler *CLIHandler) Scan() error { + cautils.ScanStartDisplay() + policyNotification := &reporthandling.PolicyNotification{ + NotificationType: reporthandling.TypeExecPostureScan, + Rules: []reporthandling.PolicyIdentifier{ + clihandler.scanInfo.PolicyIdentifier, + }, + Designators: armotypes.PortalDesignator{}, + } + switch policyNotification.NotificationType { + case reporthandling.TypeExecPostureScan: + if err := clihandler.policyHandler.HandleNotificationRequest(policyNotification, clihandler.scanInfo); err != nil { + return err + } + + default: + return fmt.Errorf("notification type '%s' Unknown", policyNotification.NotificationType) + } + return nil +} diff --git a/cmd/download.go b/cmd/download.go deleted file mode 100644 index 550c42f8..00000000 --- a/cmd/download.go +++ /dev/null @@ -1,45 +0,0 @@ -package cmd - -import ( - "fmt" - - "github.com/armosec/kubescape/cautils" - "github.com/armosec/kubescape/cautils/getter" - "github.com/spf13/cobra" -) - -var downloadInfo cautils.DownloadInfo - -var downloadCmd = &cobra.Command{ - Use: fmt.Sprintf("download framework [flags]\nSupported frameworks: %s", validFrameworks), - Short: "Download framework controls", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) != 2 { - return fmt.Errorf("requires two arguments : framework ") - } - return nil - }, - RunE: func(cmd *cobra.Command, args []string) error { - downloadInfo.FrameworkName = args[1] - g := getter.NewDownloadReleasedPolicy() - if downloadInfo.Path == "" { - downloadInfo.Path = getter.GetDefaultPath(downloadInfo.FrameworkName + ".json") - } - frameworks, err := g.GetFramework(downloadInfo.FrameworkName) - if err != nil { - return err - } - err = getter.SaveFrameworkInFile(frameworks, downloadInfo.Path) - if err != nil { - return err - } - return nil - }, -} - -func init() { - rootCmd.AddCommand(downloadCmd) - downloadInfo = cautils.DownloadInfo{} - downloadCmd.Flags().StringVarP(&downloadInfo.Path, "output", "o", "", "Output file. If specified, will store save to `~/.kubescape/.json`") -} diff --git a/cmd/framework.go b/cmd/framework.go deleted file mode 100644 index c24a4fdf..00000000 --- a/cmd/framework.go +++ /dev/null @@ -1,197 +0,0 @@ -package cmd - -import ( - "fmt" - "io" - "os" - "strings" - - "github.com/armosec/armoapi-go/armotypes" - "github.com/armosec/k8s-interface/k8sinterface" - "github.com/armosec/kubescape/cautils" - "github.com/armosec/kubescape/cautils/getter" - "github.com/armosec/kubescape/opaprocessor" - "github.com/armosec/kubescape/policyhandler" - "github.com/armosec/kubescape/resultshandling" - "github.com/armosec/kubescape/resultshandling/printer" - "github.com/armosec/kubescape/resultshandling/reporter" - "github.com/armosec/opa-utils/reporthandling" - - "github.com/spf13/cobra" -) - -var scanInfo cautils.ScanInfo -var supportedFrameworks = []string{"nsa", "mitre"} -var validFrameworks = strings.Join(supportedFrameworks, ", ") - -type CLIHandler struct { - policyHandler *policyhandler.PolicyHandler - scanInfo *cautils.ScanInfo -} - -var frameworkCmd = &cobra.Command{ - - Use: fmt.Sprintf("framework [``/`-`] [flags]\nSupported frameworks: %s", validFrameworks), - Short: fmt.Sprintf("The framework you wish to use. Supported frameworks: %s", strings.Join(supportedFrameworks, ", ")), - Long: "Execute a scan on a running Kubernetes cluster or `yaml`/`json` files (use glob) or `-` for stdin", - ValidArgs: supportedFrameworks, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 && !(cmd.Flags().Lookup("use-from").Changed) { - return fmt.Errorf("requires at least one argument") - } else if len(args) > 0 { - if !isValidFramework(strings.ToLower(args[0])) { - return fmt.Errorf(fmt.Sprintf("supported frameworks: %s", strings.Join(supportedFrameworks, ", "))) - } - } - return nil - }, - RunE: func(cmd *cobra.Command, args []string) error { - scanInfo.PolicyIdentifier = reporthandling.PolicyIdentifier{} - scanInfo.PolicyIdentifier.Kind = reporthandling.KindFramework - - if !(cmd.Flags().Lookup("use-from").Changed) { - scanInfo.PolicyIdentifier.Name = strings.ToLower(args[0]) - } - if len(args) > 0 { - if len(args[1:]) == 0 || args[1] != "-" { - scanInfo.InputPatterns = args[1:] - } else { // store stout to file - tempFile, err := os.CreateTemp(".", "tmp-kubescape*.yaml") - if err != nil { - return err - } - defer os.Remove(tempFile.Name()) - - if _, err := io.Copy(tempFile, os.Stdin); err != nil { - return err - } - scanInfo.InputPatterns = []string{tempFile.Name()} - } - } - scanInfo.Init() - cautils.SetSilentMode(scanInfo.Silent) - err := CliSetup() - if err != nil { - fmt.Fprintf(os.Stderr, "error: %v\n", err) - os.Exit(1) - } - return nil - }, -} - -func isValidFramework(framework string) bool { - return cautils.StringInSlice(supportedFrameworks, framework) != cautils.ValueNotFound -} - -func init() { - scanCmd.AddCommand(frameworkCmd) - scanInfo = cautils.ScanInfo{} - frameworkCmd.Flags().StringVar(&scanInfo.UseFrom, "use-from", "", "Load local framework object from specified path. If not used will download latest") - frameworkCmd.Flags().BoolVar(&scanInfo.UseDefault, "use-default", false, "Load local framework object from default path. If not used will download latest") - frameworkCmd.Flags().StringVar(&scanInfo.UseExceptions, "exceptions", "", "Path to an exceptions obj. If not set will download exceptions from Armo management portal") - frameworkCmd.Flags().StringVarP(&scanInfo.ExcludedNamespaces, "exclude-namespaces", "e", "", "Namespaces to exclude from scanning. Recommended: kube-system, kube-public") - frameworkCmd.Flags().StringVarP(&scanInfo.Format, "format", "f", "pretty-printer", `Output format. Supported formats: "pretty-printer"/"json"/"junit"`) - 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.Submit, "submit", "", false, "Send the scan results to Armo management portal where you can see the results in a user-friendly UI, choose your preferred compliance framework, check risk results history and trends, manage exceptions, get remediation recommendations and much more. By default the results are not submitted") - frameworkCmd.Flags().BoolVarP(&scanInfo.Local, "keep-local", "", false, "If you do not want your Kubescape results reported to Armo backend. Use this flag if you ran with the '--submit' flag in the past and you do not want to submit your current scan results") - frameworkCmd.Flags().StringVarP(&scanInfo.Account, "account", "", "", "Armo portal account ID. Default will load account ID from configMap or config file") - -} - -func CliSetup() error { - flagValidation() - - var k8s *k8sinterface.KubernetesApi - var clusterConfig cautils.IClusterConfig - if !scanInfo.ScanRunningCluster() { - k8sinterface.ConnectedToCluster = false - clusterConfig = cautils.NewEmptyConfig() - } else { - k8s = k8sinterface.NewKubernetesApi() - // setup cluster config - clusterConfig = cautils.ClusterConfigSetup(&scanInfo, k8s, getter.GetArmoAPIConnector()) - } - - processNotification := make(chan *cautils.OPASessionObj) - reportResults := make(chan *cautils.OPASessionObj) - - // policy handler setup - policyHandler := policyhandler.NewPolicyHandler(&processNotification, k8s) - - if err := clusterConfig.SetCustomerGUID(scanInfo.Account); err != nil { - fmt.Println(err) - } - - cautils.CustomerGUID = clusterConfig.GetCustomerGUID() - cautils.ClusterName = k8sinterface.GetClusterName() - - // cli handler setup - go func() { - cli := NewCLIHandler(policyHandler) - if err := cli.Scan(); err != nil { - fmt.Println(err) - os.Exit(1) - } - }() - - // processor setup - rego run - go func() { - opaprocessorObj := opaprocessor.NewOPAProcessorHandler(&processNotification, &reportResults) - opaprocessorObj.ProcessRulesListenner() - }() - - resultsHandling := resultshandling.NewResultsHandler(&reportResults, reporter.NewReportEventReceiver(), printer.NewPrinter(scanInfo.Format, scanInfo.Output)) - score := resultsHandling.HandleResults() - - // print report url - clusterConfig.GenerateURL() - - adjustedFailThreshold := float32(scanInfo.FailThreshold) / 100 - if score < adjustedFailThreshold { - return fmt.Errorf("Scan score is bellow threshold") - } - - return nil -} - -func NewCLIHandler(policyHandler *policyhandler.PolicyHandler) *CLIHandler { - return &CLIHandler{ - scanInfo: &scanInfo, - policyHandler: policyHandler, - } -} - -func (clihandler *CLIHandler) Scan() error { - cautils.ScanStartDisplay() - policyNotification := &reporthandling.PolicyNotification{ - NotificationType: reporthandling.TypeExecPostureScan, - Rules: []reporthandling.PolicyIdentifier{ - clihandler.scanInfo.PolicyIdentifier, - }, - Designators: armotypes.PortalDesignator{}, - } - switch policyNotification.NotificationType { - case reporthandling.TypeExecPostureScan: - // - if err := clihandler.policyHandler.HandleNotificationRequest(policyNotification, clihandler.scanInfo); err != nil { - return err - } - default: - return fmt.Errorf("notification type '%s' Unknown", policyNotification.NotificationType) - } - return nil -} - -func flagValidation() { - - if scanInfo.Submit && scanInfo.Local { - fmt.Println("You can use `keep-local` or `submit`, but not both") - os.Exit(1) - } - if 100 < scanInfo.FailThreshold { - fmt.Println("bad argument: out of range threshold") - os.Exit(1) - } -} diff --git a/cmd/scan.go b/cmd/scan.go deleted file mode 100644 index e421f852..00000000 --- a/cmd/scan.go +++ /dev/null @@ -1,18 +0,0 @@ -package cmd - -import ( - "github.com/spf13/cobra" -) - -// scanCmd represents the scan command -var scanCmd = &cobra.Command{ - Use: "scan", - Short: "Scan the current running cluster or yaml files", - Long: `The action you want to perform`, - Run: func(cmd *cobra.Command, args []string) { - }, -} - -func init() { - rootCmd.AddCommand(scanCmd) -} diff --git a/docs/run-options.md b/docs/run-options.md index 4a7a0a80..8a94afc0 100644 --- a/docs/run-options.md +++ b/docs/run-options.md @@ -51,7 +51,7 @@ kubescape scan framework nsa --exclude-namespaces kube-system,kube-public --form kubescape scan framework nsa --exclude-namespaces kube-system,kube-public --format junit --output results.xml ``` -* Scan with exceptions, objects with exceptions will be presented as `warning` and not `fail` +* Scan with exceptions, objects with exceptions will be presented as `warning` and not `fail` ``` kubescape scan framework nsa --exceptions examples/exceptions.json ``` diff --git a/examples/cronjob/ks-cronjob.yaml b/examples/cronjob/ks-cronjob.yaml new file mode 100644 index 00000000..49f46efe --- /dev/null +++ b/examples/cronjob/ks-cronjob.yaml @@ -0,0 +1,126 @@ +--- +# ------------------- Kubescape Service Account ------------------- # +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app: kubescape + name: kubescape-discovery + namespace: kubescape + +--- +# ------------------- Kubescape Role & Role Binding ------------------- # +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: kubescape-discovery-role + namespace: kubescape +rules: +- apiGroups: ["*"] + resources: ["*"] + verbs: ["get", "list", "describe"] + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: kubescape-discovery-binding + namespace: kubescape +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: kubescape-discovery-role +subjects: +- kind: ServiceAccount + name: kubescape-discovery + +--- +# ------------------- Kubescape Cluster Role & Cluster Role Binding ------------------- # +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: kubescape-discovery-clusterroles + # "namespace" omitted since ClusterRoles are not namespaced +rules: +- apiGroups: ["*"] + resources: ["*"] + verbs: ["get", "list", "describe"] + +--- +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: kubescape-discovery-role-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: kubescape-discovery-clusterroles +subjects: +- kind: ServiceAccount + name: kubescape-discovery + namespace: kubescape + +--- +# ------------------- Kubescape User/Customer GUID ------------------- # +kind: ConfigMap +apiVersion: v1 +metadata: + name: kubescape-configmap + labels: + app: kubescape + namespace: kubescape +data: + config.json: | + { + "adminMail": "", + "customerGUID": "", + "clusterName": "", + "invitationParam": "" + } + +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: kubescape + labels: + app: kubescape + namespace: kubescape +spec: + # ┌────────────────── timezone (optional) + # | ┌───────────── minute (0 - 59) + # | │ ┌───────────── hour (0 - 23) + # | │ │ ┌───────────── day of the month (1 - 31) + # | │ │ │ ┌───────────── month (1 - 12) + # | │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday; + # | │ │ │ │ │ 7 is also Sunday on some systems) + # | │ │ │ │ │ + # | │ │ │ │ │ + # CRON_TZ=UTC * * * * * + schedule: "0 0 1 * *" + jobTemplate: + spec: + template: + spec: + containers: + - name: kubescape + image: quay.io/armosec/kubescape:latset + imagePullPolicy: IfNotPresent + command: ["/bin/sh","-c"] + args: + - kubescape scan framework nsa --exclude-namespaces kube-system,kube-public + #- kubescape scan framework nsa --use-default --exclude-namespaces kube-system,kube-public + volumeMounts: + - name: kubescape-config-volume + mountPath: /root/.kubescape/config.json + subPath: config.json + restartPolicy: OnFailure + serviceAccountName: kubescape-discovery + volumes: + - name: kubescape-config-volume + configMap: + name: kubescape-configmap + +--- + + diff --git a/go.mod b/go.mod index 9bce8496..0e3cbc03 100644 --- a/go.mod +++ b/go.mod @@ -3,36 +3,24 @@ module github.com/armosec/kubescape go 1.17 require ( + github.com/armosec/armoapi-go v0.0.8 + github.com/armosec/k8s-interface v0.0.5 + github.com/armosec/opa-utils v0.0.13 + github.com/armosec/utils-go v0.0.3 github.com/briandowns/spinner v1.16.0 - github.com/coreos/go-oidc v2.2.1+incompatible // indirect - github.com/docker/docker v20.10.9+incompatible // indirect - github.com/docker/go-connections v0.4.0 // indirect - github.com/docker/go-units v0.4.0 // indirect github.com/enescakir/emoji v1.0.0 - github.com/fatih/color v1.12.0 - github.com/francoispqt/gojay v1.2.13 // indirect - github.com/gofrs/uuid v4.0.0+incompatible + github.com/fatih/color v1.13.0 + github.com/gofrs/uuid v4.1.0+incompatible github.com/golang/glog v1.0.0 - github.com/mattn/go-isatty v0.0.13 + github.com/mattn/go-isatty v0.0.14 github.com/olekukonko/tablewriter v0.0.5 github.com/open-policy-agent/opa v0.33.1 - github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.0.1 // indirect github.com/satori/go.uuid v1.2.0 github.com/spf13/cobra v1.2.1 - golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1 // indirect gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.22.2 k8s.io/apimachinery v0.22.2 k8s.io/client-go v0.22.2 - sigs.k8s.io/controller-runtime v0.10.2 // indirect -) - -require ( - github.com/armosec/armoapi-go v0.0.7 - github.com/armosec/k8s-interface v0.0.5 - github.com/armosec/opa-utils v0.0.7 - github.com/armosec/utils-go v0.0.3 ) require ( @@ -45,9 +33,15 @@ require ( github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/OneOfOne/xxhash v1.2.8 // indirect github.com/armosec/utils-k8s-go v0.0.1 // indirect + github.com/coreos/go-oidc v2.2.1+incompatible // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/docker/docker v20.10.9+incompatible // indirect + github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-units v0.4.0 // indirect github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect + github.com/francoispqt/gojay v1.2.13 // indirect github.com/ghodss/yaml v1.0.0 // indirect + github.com/go-gota/gota v0.12.0 // indirect github.com/go-logr/logr v0.4.0 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect @@ -58,10 +52,12 @@ require ( github.com/imdario/mergo v0.3.12 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/json-iterator/go v1.1.11 // indirect - github.com/mattn/go-colorable v0.1.8 // indirect + github.com/mattn/go-colorable v0.1.9 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.0.1 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pquerna/cachecontrol v0.1.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect @@ -69,12 +65,17 @@ require ( github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b // indirect + go.uber.org/atomic v1.7.0 // indirect + go.uber.org/multierr v1.6.0 // indirect + go.uber.org/zap v1.19.1 // indirect golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 // indirect golang.org/x/net v0.0.0-20210825183410-e898025ed96a // indirect + golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1 // indirect golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf // indirect golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect golang.org/x/text v0.3.6 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect + gonum.org/v1/gonum v0.9.1 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.27.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect @@ -82,6 +83,7 @@ require ( gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect k8s.io/klog/v2 v2.9.0 // indirect k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a // indirect + sigs.k8s.io/controller-runtime v0.10.2 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect sigs.k8s.io/yaml v1.2.0 // indirect ) diff --git a/go.sum b/go.sum index 07805434..842bf3e4 100644 --- a/go.sum +++ b/go.sum @@ -44,6 +44,7 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7 dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= +gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= @@ -70,6 +71,7 @@ github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8 github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -82,12 +84,13 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armosec/armoapi-go v0.0.2/go.mod h1:vIK17yoKbJRQyZXWWLe3AqfqCRITxW8qmSkApyq5xFs= -github.com/armosec/armoapi-go v0.0.7 h1:SN13+iYrIkxgatU+MwuWnSlhxP1G7rZP7dC8us2I7v0= github.com/armosec/armoapi-go v0.0.7/go.mod h1:iaVVGyc23QGGzAdv4n+szGQg3Rbpixn9yQTU3qWRpaw= +github.com/armosec/armoapi-go v0.0.8 h1:JPa9rZynuE2RucamDh6dsy/sjCScmWDsyt1zagJFCDo= +github.com/armosec/armoapi-go v0.0.8/go.mod h1:iaVVGyc23QGGzAdv4n+szGQg3Rbpixn9yQTU3qWRpaw= github.com/armosec/k8s-interface v0.0.5 h1:DWQXZNMSsYQeLQ6xpB21ueFMR9oFnz28iWQTNn31TAk= github.com/armosec/k8s-interface v0.0.5/go.mod h1:xxS+V5QT3gVQTwZyAMMDrYLWGrfKOpiJ7Jfhfa0w9sM= -github.com/armosec/opa-utils v0.0.7 h1:cafKzdQcCVqaOz6zNdne+wxNqajZFd6Ad2KJpHM3gF8= -github.com/armosec/opa-utils v0.0.7/go.mod h1:fxPGsKEKOf0FDQVciKiCTZv4iibRkbld5lK1hyUyVcA= +github.com/armosec/opa-utils v0.0.13 h1:QkmmYX0lzC7ZNGetyD8ysRKQHgJhjMfvRUW2cp+hz2o= +github.com/armosec/opa-utils v0.0.13/go.mod h1:E0mFTVx+4BYAVvO2hxWnIniv/IZIogRCak8BkKd7KK4= github.com/armosec/utils-go v0.0.2/go.mod h1:itWmRLzRdsnwjpEOomL0mBWGnVNNIxSjDAdyc+b0iUo= github.com/armosec/utils-go v0.0.3 h1:uyQI676yRciQM0sSN9uPoqHkbspTxHO0kmzXhBeE/xU= github.com/armosec/utils-go v0.0.3/go.mod h1:itWmRLzRdsnwjpEOomL0mBWGnVNNIxSjDAdyc+b0iUo= @@ -96,6 +99,7 @@ github.com/armosec/utils-k8s-go v0.0.1/go.mod h1:qrU4pmY2iZsOb39Eltpm0sTTNM3E4pm github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-sdk-go v1.41.1/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= @@ -104,6 +108,7 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/briandowns/spinner v1.16.0 h1:DFmp6hEaIx2QXXuqSJmtfSBSAjRmpGiKG6ip2Wm/yOs= github.com/briandowns/spinner v1.16.0/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX3FScO+3/ZPQ= @@ -181,10 +186,12 @@ github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMi github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs= github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc= -github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= @@ -201,12 +208,19 @@ github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= +github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= +github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= +github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gota/gota v0.12.0 h1:T5BDg1hTf5fZ/CO+T/N0E+DDqUhvoKBl+UVckgcAAQg= +github.com/go-gota/gota v0.12.0/go.mod h1:UT+NsWpZC/FhaOyWb9Hui0jXg0Iq8e/YugZHTbyW/34= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= @@ -227,13 +241,15 @@ github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg78 github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.1.0+incompatible h1:sIa2eCvUTwgjbqXrPLfNwUf9S3i3mpH1O1atV+iL/Wk= +github.com/gofrs/uuid v4.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= @@ -375,6 +391,8 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -403,13 +421,13 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= -github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA= -github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -472,6 +490,8 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9 github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/peterh/liner v0.0.0-20170211195444-bf27d3ba8e1d/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= +github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= +github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -514,6 +534,7 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= @@ -638,6 +659,7 @@ go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.1.11-0.20210813005559-691160354723 h1:sHOAIxRGBp443oHZIPB+HsUGaksVCXVQENPxwTfQdH4= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= @@ -663,18 +685,30 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -745,6 +779,7 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -836,6 +871,7 @@ golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -870,11 +906,13 @@ golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -889,6 +927,7 @@ golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -935,6 +974,14 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/gonum v0.9.1 h1:HCWmqqNoELL0RAQeKBXWtkp04mGk8koafcB4He6+uhc= +gonum.org/v1/gonum v0.9.1/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= @@ -1121,6 +1168,7 @@ k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2R k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a h1:8dYfu/Fc9Gz2rNJKB9IQRGgQOh2clmRzNIPPY1xLY5g= k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= diff --git a/install.sh b/install.sh index c9cc5ceb..2d0173bd 100755 --- a/install.sh +++ b/install.sh @@ -53,6 +53,6 @@ echo -e "\033[0m" $KUBESCAPE_EXEC version echo -echo -e "\033[35mUsage: $ $KUBESCAPE_EXEC scan framework nsa --exclude-namespaces kube-system,kube-public" +echo -e "\033[35mUsage: $ $KUBESCAPE_EXEC scan framework nsa" echo -e "\033[0m" diff --git a/main.go b/main.go index 114f855c..956b6622 100644 --- a/main.go +++ b/main.go @@ -4,7 +4,7 @@ import ( "fmt" "os" - "github.com/armosec/kubescape/cmd" + "github.com/armosec/kubescape/clihandler/cmd" ) func main() { diff --git a/policyhandler/handlenotification.go b/policyhandler/handlenotification.go index 9da07cac..76ecbb6e 100644 --- a/policyhandler/handlenotification.go +++ b/policyhandler/handlenotification.go @@ -10,6 +10,11 @@ import ( "github.com/armosec/k8s-interface/k8sinterface" ) +var supportedFrameworks = []reporthandling.PolicyIdentifier{ + {Kind: "Framework", Name: "nsa"}, + {Kind: "Framework", Name: "mitre"}, +} + // PolicyHandler - type PolicyHandler struct { k8s *k8sinterface.KubernetesApi @@ -59,7 +64,7 @@ func (policyHandler *PolicyHandler) HandleNotificationRequest(notification *repo func (policyHandler *PolicyHandler) getPolicies(notification *reporthandling.PolicyNotification) ([]reporthandling.Framework, []armotypes.PostureExceptionPolicy, error) { - cautils.ProgressTextDisplay("Downloading/Loading framework definitions") + cautils.ProgressTextDisplay("Downloading/Loading policy definitions") frameworks, exceptions, err := policyHandler.GetPoliciesFromBackend(notification) if err != nil { @@ -70,7 +75,8 @@ func (policyHandler *PolicyHandler) getPolicies(notification *reporthandling.Pol err := fmt.Errorf("could not download any policies, please check previous logs") return frameworks, exceptions, err } - cautils.SuccessTextDisplay("Downloaded/Loaded framework") + //if notification.Rules + cautils.SuccessTextDisplay("Downloaded/Loaded policy") return frameworks, exceptions, nil } @@ -78,7 +84,10 @@ func (policyHandler *PolicyHandler) getPolicies(notification *reporthandling.Pol func (policyHandler *PolicyHandler) getResources(notification *reporthandling.PolicyNotification, opaSessionObj *cautils.OPASessionObj, scanInfo *cautils.ScanInfo) (*cautils.K8SResources, error) { var k8sResources *cautils.K8SResources var err error - if k8sinterface.ConnectedToCluster { + if k8sinterface.ConnectedToCluster { // TODO - use interface + if opaSessionObj.PostureReport.ClusterAPIServerInfo, err = policyHandler.k8s.KubernetesClient.Discovery().ServerVersion(); err != nil { + cautils.ErrorDisplay(fmt.Sprintf("Failed to discover API server inforamtion: %v", err)) + } k8sResources, err = policyHandler.getK8sResources(opaSessionObj.Frameworks, ¬ification.Designators, scanInfo.ExcludedNamespaces) } else { k8sResources, err = policyHandler.loadResources(opaSessionObj.Frameworks, scanInfo) diff --git a/policyhandler/handlepullpolicies.go b/policyhandler/handlepullpolicies.go index 34f0f7ec..0762a05e 100644 --- a/policyhandler/handlepullpolicies.go +++ b/policyhandler/handlepullpolicies.go @@ -13,7 +13,6 @@ func (policyHandler *PolicyHandler) GetPoliciesFromBackend(notification *reporth var errs error frameworks := []reporthandling.Framework{} exceptionPolicies := []armotypes.PostureExceptionPolicy{} - // Get - cacli opa get for _, rule := range notification.Rules { switch rule.Kind { @@ -30,7 +29,23 @@ func (policyHandler *PolicyHandler) GetPoliciesFromBackend(notification *reporth } return nil, nil, fmt.Errorf("kind: %v, name: %s, error: %s", rule.Kind, rule.Name, err.Error()) } - + case reporthandling.KindControl: + receivedControls, recExceptionPolicies, err := policyHandler.getControl(rule.Name) + if receivedControls != nil { + f := reporthandling.Framework{ + Controls: receivedControls, + } + frameworks = append(frameworks, f) + if recExceptionPolicies != nil { + exceptionPolicies = append(exceptionPolicies, recExceptionPolicies...) + } + } else if err != nil { + if strings.Contains(err.Error(), "unsupported protocol scheme") { + err = fmt.Errorf("failed to download from GitHub release, try running with `--use-default` flag") + } + return nil, nil, fmt.Errorf("error: %s", err.Error()) + } + // TODO: add case for control from file default: err := fmt.Errorf("missing rule kind, expected: %s", reporthandling.KindFramework) errs = fmt.Errorf("%s", err.Error()) @@ -52,3 +67,25 @@ func (policyHandler *PolicyHandler) getFrameworkPolicies(policyName string) (*re return receivedFramework, receivedException, nil } + +// Get control by name +func (policyHandler *PolicyHandler) getControl(policyName string) ([]reporthandling.Control, []armotypes.PostureExceptionPolicy, error) { + + controls := []reporthandling.Control{} + + control, err := policyHandler.getters.PolicyGetter.GetControl(policyName) + if err != nil { + return nil, nil, err + } + if control == nil { + return nil, nil, fmt.Errorf("control not found") + } + controls = append(controls, *control) + + exceptions, err := policyHandler.getters.ExceptionsGetter.GetExceptions(cautils.CustomerGUID, cautils.ClusterName) + if err != nil { + return controls, nil, err + } + + return controls, exceptions, nil +} diff --git a/policyhandler/repositoryscanner.go b/policyhandler/repositoryscanner.go new file mode 100644 index 00000000..a568e8c5 --- /dev/null +++ b/policyhandler/repositoryscanner.go @@ -0,0 +1,155 @@ +package policyhandler + +import ( + "encoding/json" + "fmt" + "net/http" + "strings" + + "github.com/armosec/kubescape/cautils/getter" +) + +type IRepository interface { + setBranch(string) error + setTree() error + getYamlFromTree() []string +} + +type innerTree struct { + Path string `json:"path"` +} +type tree struct { + InnerTrees []innerTree `json:"tree"` +} + +type GitHubRepository struct { + host string + name string // / + branch string + tree tree +} +type githubDefaultBranchAPI struct { + DefaultBranch string `json:"default_branch"` +} + +func NewGitHubRepository(rep string) *GitHubRepository { + return &GitHubRepository{ + host: "github", + name: rep, + } +} + +func ScanRepository(command string, branchOptional string) ([]string, error) { + repo, err := getRepository(command) + if err != nil { + return nil, err + } + + err = repo.setBranch(branchOptional) + if err != nil { + return nil, err + } + + err = repo.setTree() + if err != nil { + return nil, err + } + + // get all paths that are of the yaml type, and build them into a valid url + return repo.getYamlFromTree(), nil +} + +func getHostAndRepoName(url string) (string, string, error) { + splitUrl := strings.Split(url, "/") + + if len(splitUrl) != 5 { + return "", "", fmt.Errorf("failed to pars url: %s", url) + } + + hostUrl := splitUrl[2] // github.com, gitlab.com, etc. + repository := splitUrl[3] + "/" + strings.Split(splitUrl[4], ".")[0] // user/reposetory + + return hostUrl, repository, nil +} + +func getRepository(url string) (IRepository, error) { + hostUrl, repoName, err := getHostAndRepoName(url) + if err != nil { + return nil, err + } + + var repo IRepository + switch repoHost := strings.Split(hostUrl, ".")[0]; repoHost { + case "github": + repo = NewGitHubRepository(repoName) + default: + return nil, fmt.Errorf("unknown repository host: %s", repoHost) + } + + // Returns the host-url, and the part of the user and repository from the url + return repo, nil +} + +func (g *GitHubRepository) setBranch(branchOptional string) error { + // Checks whether the repository type is a master or another type. + // By default it is "master", unless the branchOptional came with a value + if branchOptional == "" { + + body, err := getter.HttpGetter(&http.Client{}, g.defaultBranchAPI()) + if err != nil { + return err + } + + var data githubDefaultBranchAPI + err = json.Unmarshal([]byte(body), &data) + if err != nil { + return err + } + g.branch = data.DefaultBranch + } else { + g.branch = branchOptional + } + return nil +} + +func (g *GitHubRepository) defaultBranchAPI() string { + return fmt.Sprintf("https://api.github.com/repos/%s", g.name) +} + +func (g *GitHubRepository) setTree() error { + body, err := getter.HttpGetter(&http.Client{}, g.treeAPI()) + if err != nil { + return err + } + + // press all tree to json + var tree tree + err = json.Unmarshal([]byte(body), &tree) + if err != nil { + return fmt.Errorf("failed to unmarshal response body from '%s', reason: %s", g.treeAPI(), err.Error()) + // fmt.Printf("failed to unmarshal response body from '%s', reason: %s", urlCommand, err.Error()) + // return nil + } + g.tree = tree + + return nil +} + +func (g *GitHubRepository) treeAPI() string { + return fmt.Sprintf("https://api.github.com/repos/%s/git/trees/%s?recursive=1", g.name, g.branch) +} + +// return a list of yaml for a given repository tree +func (g *GitHubRepository) getYamlFromTree() []string { + var urls []string + for _, path := range g.tree.InnerTrees { + if strings.HasSuffix(path.Path, ".yaml") { + urls = append(urls, fmt.Sprintf("%s/%s", g.rowYamlUrl(), path.Path)) + } + } + return urls +} + +func (g *GitHubRepository) rowYamlUrl() string { + return fmt.Sprintf("https://raw.githubusercontent.com/%s/%s", g.name, g.branch) +} diff --git a/policyhandler/urlloader.go b/policyhandler/urlloader.go index 151aeaa5..faa8f634 100644 --- a/policyhandler/urlloader.go +++ b/policyhandler/urlloader.go @@ -28,9 +28,18 @@ func listUrls(patterns []string) []string { urls := []string{} for i := range patterns { if strings.HasPrefix(patterns[i], "http") { - urls = append(urls, patterns[i]) + if !isYaml(patterns[i]) || !isJson(patterns[i]) { // if url of repo + if yamls, err := ScanRepository(patterns[i], ""); err == nil { // TODO - support branch + urls = append(urls, yamls...) + } else { + fmt.Print(err) // TODO - handle errors + } + } else { // url of single file + urls = append(urls, patterns[i]) + } } } + return urls } diff --git a/resultshandling/printer/junit.go b/resultshandling/printer/junit.go index d3b15c38..370a5d0a 100644 --- a/resultshandling/printer/junit.go +++ b/resultshandling/printer/junit.go @@ -17,9 +17,11 @@ type JUnitTestSuites struct { type JUnitTestSuite struct { XMLName xml.Name `xml:"testsuite"` Tests int `xml:"tests,attr"` - Failures int `xml:"failures,attr"` Time string `xml:"time,attr"` Name string `xml:"name,attr"` + Resources int `xml:"resources,attr"` + Excluded int `xml:"excluded,attr"` + Failed int `xml:"filed,attr"` Properties []JUnitProperty `xml:"properties>property,omitempty"` TestCases []JUnitTestCase `xml:"testcase"` } @@ -30,6 +32,9 @@ type JUnitTestCase struct { Classname string `xml:"classname,attr"` Name string `xml:"name,attr"` Time string `xml:"time,attr"` + Resources int `xml:"resources,attr"` + Excluded int `xml:"excluded,attr"` + Failed int `xml:"filed,attr"` SkipMessage *JUnitSkipMessage `xml:"skipped,omitempty"` Failure *JUnitFailure `xml:"failure,omitempty"` } @@ -55,7 +60,12 @@ type JUnitFailure struct { func convertPostureReportToJunitResult(postureResult *reporthandling.PostureReport) (*JUnitTestSuites, error) { juResult := JUnitTestSuites{XMLName: xml.Name{Local: "Kubescape scan results"}} for _, framework := range postureResult.FrameworkReports { - suite := JUnitTestSuite{Name: framework.Name} + suite := JUnitTestSuite{ + Name: framework.Name, + Resources: framework.GetNumberOfResources(), + Excluded: framework.GetNumberOfWarningResources(), + Failed: framework.GetNumberOfFailedResources(), + } for _, controlReports := range framework.ControlReports { suite.Tests = suite.Tests + 1 testCase := JUnitTestCase{} @@ -63,9 +73,12 @@ func convertPostureReportToJunitResult(postureResult *reporthandling.PostureRepo testCase.Classname = "Kubescape" testCase.Time = "0" if 0 < len(controlReports.RuleReports[0].RuleResponses) { - suite.Failures = suite.Failures + 1 + + testCase.Resources = framework.GetNumberOfResources() + testCase.Excluded = framework.GetNumberOfWarningResources() + testCase.Failed = framework.GetNumberOfFailedResources() failure := JUnitFailure{} - failure.Message = fmt.Sprintf("%d resources failed", len(controlReports.RuleReports[0].RuleResponses)) + failure.Message = fmt.Sprintf("%d resources failed", testCase.Failed) for _, ruleResponses := range controlReports.RuleReports[0].RuleResponses { failure.Contents = fmt.Sprintf("%s\n%s", failure.Contents, ruleResponses.AlertMessage) } diff --git a/resultshandling/printer/printresults.go b/resultshandling/printer/printresults.go index 8d8fc080..50a624c4 100644 --- a/resultshandling/printer/printresults.go +++ b/resultshandling/printer/printresults.go @@ -54,7 +54,7 @@ func calculatePostureScore(postureReport *reporthandling.PostureReport) float32 } func (printer *Printer) ActionPrint(opaSessionObj *cautils.OPASessionObj) float32 { - var score float32 + score := calculatePostureScore(opaSessionObj.PostureReport) if printer.printerType == PrettyPrinter { printer.SummarySetup(opaSessionObj.PostureReport) @@ -67,6 +67,7 @@ func (printer *Printer) ActionPrint(opaSessionObj *cautils.OPASessionObj) float3 os.Exit(1) } printer.writer.Write(postureReportStr) + fmt.Printf("\nFinal score: %d\n", int(score*100)) } else if printer.printerType == JunitResultPrinter { junitResult, err := convertPostureReportToJunitResult(opaSessionObj.PostureReport) if err != nil { @@ -79,13 +80,12 @@ func (printer *Printer) ActionPrint(opaSessionObj *cautils.OPASessionObj) float3 os.Exit(1) } printer.writer.Write(postureReportStr) + fmt.Printf("\nFinal score: %d\n", int(score*100)) } else if !cautils.IsSilent() { fmt.Println("unknown output printer") os.Exit(1) } - score = calculatePostureScore(opaSessionObj.PostureReport) - return score } @@ -101,16 +101,16 @@ func (printer *Printer) SummarySetup(postureReport *reporthandling.PostureReport continue } workloadsSummary := listResultSummary(cr.RuleReports) - mapResources := groupByNamespace(workloadsSummary) printer.summary[cr.Name] = ControlSummary{ - TotalResources: cr.GetNumberOfResources(), - TotalFailed: cr.GetNumberOfFailedResources(), - TotalWarnign: cr.GetNumberOfWarningResources(), - WorkloadSummary: mapResources, - Description: cr.Description, - Remediation: cr.Remediation, - ListInputKinds: cr.ListControlsInputKinds(), + TotalResources: cr.GetNumberOfResources(), + TotalFailed: cr.GetNumberOfFailedResources(), + TotalWarnign: cr.GetNumberOfWarningResources(), + FailedWorkloads: groupByNamespace(workloadsSummary, workloadSummaryFailed), + ExcludedWorkloads: groupByNamespace(workloadsSummary, workloadSummaryExclude), + Description: cr.Description, + Remediation: cr.Remediation, + ListInputKinds: cr.ListControlsInputKinds(), } } } @@ -120,8 +120,7 @@ func (printer *Printer) PrintResults() { for i := 0; i < len(printer.sortedControlNames); i++ { controlSummary := printer.summary[printer.sortedControlNames[i]] printer.printTitle(printer.sortedControlNames[i], &controlSummary) - printer.printResult(printer.sortedControlNames[i], &controlSummary) - + printer.printResources(&controlSummary) if printer.summary[printer.sortedControlNames[i]].TotalResources > 0 { printer.printSummary(printer.sortedControlNames[i], &controlSummary) } @@ -144,7 +143,7 @@ func (printer *Printer) printSummary(controlName string, controlSummary *Control func (printer *Printer) printTitle(controlName string, controlSummary *ControlSummary) { cautils.InfoDisplay(printer.writer, "[control: %s] ", controlName) - if controlSummary.TotalResources == 0 && len(controlSummary.ListInputKinds) > 0 { + if controlSummary.TotalResources == 0 { cautils.InfoDisplay(printer.writer, "resources not found %v\n", emoji.ConfusedFace) } else if controlSummary.TotalFailed != 0 { cautils.FailureDisplay(printer.writer, "failed %v\n", emoji.SadButRelievedFace) @@ -157,10 +156,24 @@ func (printer *Printer) printTitle(controlName string, controlSummary *ControlSu cautils.DescriptionDisplay(printer.writer, "Description: %s\n", controlSummary.Description) } -func (printer *Printer) printResult(controlName string, controlSummary *ControlSummary) { +func (printer *Printer) printResources(controlSummary *ControlSummary) { + + if len(controlSummary.FailedWorkloads) > 0 { + cautils.FailureDisplay(printer.writer, "Failed:\n") + printer.printGroupedResources(controlSummary.FailedWorkloads) + } + if len(controlSummary.ExcludedWorkloads) > 0 { + cautils.WarningDisplay(printer.writer, "Excluded:\n") + printer.printGroupedResources(controlSummary.ExcludedWorkloads) + } + +} + +func (printer *Printer) printGroupedResources(workloads map[string][]WorkloadSummary) { indent := INDENT - for ns, rsc := range controlSummary.WorkloadSummary { + + for ns, rsc := range workloads { preIndent := indent if ns != "" { cautils.SimpleDisplay(printer.writer, "%sNamespace %s\n", indent, ns) @@ -207,7 +220,7 @@ func percentage(big, small int) int { func generateFooter(numControlers, sumFailed, sumWarning, sumTotal int) []string { // Control name | # failed resources | all resources | % success row := []string{} - row = append(row, fmt.Sprintf("%d", numControlers)) + row = append(row, "Resource Summary") //fmt.Sprintf(""%d", numControlers")) row = append(row, fmt.Sprintf("%d", sumFailed)) row = append(row, fmt.Sprintf("%d", sumWarning)) row = append(row, fmt.Sprintf("%d", sumTotal)) @@ -223,7 +236,8 @@ func (printer *Printer) PrintSummaryTable() { summaryTable.SetAutoWrapText(false) summaryTable.SetHeader(generateHeader()) summaryTable.SetHeaderLine(true) - summaryTable.SetAlignment(tablewriter.ALIGN_LEFT) + alignments := []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER} + summaryTable.SetColumnAlignment(alignments) for i := 0; i < len(printer.sortedControlNames); i++ { controlSummary := printer.summary[printer.sortedControlNames[i]] diff --git a/resultshandling/printer/summary.go b/resultshandling/printer/summary.go index 1bc90911..9df245f6 100644 --- a/resultshandling/printer/summary.go +++ b/resultshandling/printer/summary.go @@ -13,13 +13,14 @@ func NewSummary() Summary { } type ControlSummary struct { - TotalResources int - TotalFailed int - TotalWarnign int - Description string - Remediation string - ListInputKinds []string - WorkloadSummary map[string][]WorkloadSummary // :[] + TotalResources int + TotalFailed int + TotalWarnign int + Description string + Remediation string + ListInputKinds []string + FailedWorkloads map[string][]WorkloadSummary // :[] + ExcludedWorkloads map[string][]WorkloadSummary // :[] } type WorkloadSummary struct { @@ -41,3 +42,11 @@ func (controlSummary *ControlSummary) ToSlice() []string { func (workloadSummary *WorkloadSummary) ToString() string { return fmt.Sprintf("/%s/%s/%s/%s", workloadSummary.Group, workloadSummary.Namespace, workloadSummary.Kind, workloadSummary.Name) } + +func workloadSummaryFailed(workloadSummary *WorkloadSummary) bool { + return workloadSummary.Exception == nil +} + +func workloadSummaryExclude(workloadSummary *WorkloadSummary) bool { + return workloadSummary.Exception != nil && workloadSummary.Exception.IsAlertOnly() +} diff --git a/resultshandling/printer/summeryhelpers.go b/resultshandling/printer/summeryhelpers.go index 3cd11edb..cf7a4e43 100644 --- a/resultshandling/printer/summeryhelpers.go +++ b/resultshandling/printer/summeryhelpers.go @@ -8,14 +8,16 @@ import ( ) // Group workloads by namespace - return {"namespace": <[]WorkloadSummary>} -func groupByNamespace(resources []WorkloadSummary) map[string][]WorkloadSummary { +func groupByNamespace(resources []WorkloadSummary, status func(workloadSummary *WorkloadSummary) bool) map[string][]WorkloadSummary { mapResources := make(map[string][]WorkloadSummary) for i := range resources { - if r, ok := mapResources[resources[i].Namespace]; ok { - r = append(r, resources[i]) - mapResources[resources[i].Namespace] = r - } else { - mapResources[resources[i].Namespace] = []WorkloadSummary{resources[i]} + if status(&resources[i]) { + if r, ok := mapResources[resources[i].Namespace]; ok { + r = append(r, resources[i]) + mapResources[resources[i].Namespace] = r + } else { + mapResources[resources[i].Namespace] = []WorkloadSummary{resources[i]} + } } } return mapResources diff --git a/resultshandling/results.go b/resultshandling/results.go index 64ace984..2db5e8f0 100644 --- a/resultshandling/results.go +++ b/resultshandling/results.go @@ -20,13 +20,16 @@ func NewResultsHandler(opaSessionObj *chan *cautils.OPASessionObj, reporterObj * } } -func (resultsHandler *ResultsHandler) HandleResults() float32 { +func (resultsHandler *ResultsHandler) HandleResults(scanInfo cautils.ScanInfo) float32 { opaSessionObj := <-*resultsHandler.opaSessionObj score := resultsHandler.printerObj.ActionPrint(opaSessionObj) - resultsHandler.reporterObj.ActionSendReportListenner(opaSessionObj) + // Don't send report for control scan + if scanInfo.FrameworkScan { // TODO - use interface for ActionSendReportListenner + resultsHandler.reporterObj.ActionSendReportListenner(opaSessionObj) + } return score }