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 @@
[](https://github.com/armosec/kubescape/actions/workflows/build.yaml)
[](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
}