From eb16440ba646e64ff6123ef4bcaca981d084c7c5 Mon Sep 17 00:00:00 2001 From: David Wertenteil Date: Mon, 19 Sep 2022 08:41:40 +0300 Subject: [PATCH 1/2] Enhancing CLI flags (#835) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix issue for scanning list obj * Fix go mod in httphandler pkg * Broken links fix in roadmap.md Planning, backlog, and wishlist links were not taking to the required section. * override infoMap only if it's not nil * improved icon of kubescape in readme * Support scanning several files * gramatical improvements * docs(readme): Star → star * Fix issues according to review * Handle with issues caused by updating opa-utils * Fix scanning ListObj following reviews * Update core/pkg/resourcehandler/filesloader.go Co-authored-by: Vlad Klokun * Update completion.go * Added fixed control input * update go.mod * Print chart name log when fail to generate * Change formatting to %s * Added resource prioritization information, raw resource will be sent on the result object * Merging typo fixes from master (#772) * greetings * Update aws.sh simplified the comment * typo: In the title and h1 element Their was a typo in index.html file. * punctuation changes * docs : added gitpod badge in readme.md * fixed typos * ƒ some grammar mistake is corrected inPULL_REQUEST_TEMPLATE.md file * Updated README.md file Added link to CONTRIBUTING.md file in a line in README. * Added link to code of conduct file I have added link to the code of conduct file and fixed some problems in the Readme file. * Fixed readme * Added alpine tag Adding alpine tag instead of latest and removing repeating commands * roadmap.md file is modified * Automatically Close "Typo" labelled Issue * build.py is modified * modified PR template * Fixed some typos in feature_request.md "." at the end of the headings were missing and all the text were in same line. Now this gives a clear and concise view of the texts. * fixed the typo in docs/index.html Found and fixed typo in the 'alt' attribute of img tag * Update PULL_REQUEST_TEMPLATE.md Co-authored-by: Krishna Agarwal Co-authored-by: Saswata Senapati <74651639+saswat16@users.noreply.github.com> Co-authored-by: Rahul Singh <110548934+rahuldhirendersingh@users.noreply.github.com> Co-authored-by: deepuyadav004 Co-authored-by: kartik <97971066+kartikgajjar7@users.noreply.github.com> Co-authored-by: Rounak-28 <95576871+Rounak-28@users.noreply.github.com> Co-authored-by: pwnb0y Co-authored-by: Ben Hirschberg <59160382+slashben@users.noreply.github.com> Co-authored-by: Saptarshi Sarkar Co-authored-by: Rahul Surwade <93492791+RahulSurwade08@users.noreply.github.com> Co-authored-by: Suhas Gumma <43647369+suhasgumma@users.noreply.github.com> Co-authored-by: Kamal Nayan <95926324+legendarykamal@users.noreply.github.com> Co-authored-by: TarangVerma <90996971+TarangVerma@users.noreply.github.com> Co-authored-by: avikittu <65793296+avikittu@users.noreply.github.com> * update logger version * update logger version (#773) * Fixed: Kubescape fails to authenticate remote private Github repo (#721) * grammar error fixer in CONTRIBUTING.md * scanning private git repository is available * giturl to gitapi * NO TOKEN error functionality added * Used GetToken method of giturl.IGitAPPI for auth Co-authored-by: satyam kale Co-authored-by: Ben Hirschberg <59160382+slashben@users.noreply.github.com> * bump opa-utils to 181 * Option to force enable color output (closes #560) (#767) * Option to force enable color output (closes #560) (cherry picked from commit 4f951781ee8dd6bb451ac7d159787f47e4b07379) * Update go.mod * update scanner image * Update host scanner image (#774) * update logger version * update scanner image * remove windows exe extension * Remove windows extension build (#775) * update logger version * update scanner image * remove windows exe extension * commened out prioritization logic * Edit Junit output (#802) * Edit Junit output * Update go sum * Following review * update AdoptClusterName * Print line separator only if some controls failed (#813) * removed the extra 'download' word from the example (#810) it was confusing to understand the download command because there was an extra 'download' mentioned * Prioritization (#815) * removed commented out code * Added attack tracks information to prioritization algorithm * bump opa-utils * go mod tidy * go mod tidy * CR changes * Issue 613 cluster name (#783) * added --clusterName flag (#613) Signed-off-by: Anubhav Gupta * update flag name to --cluster-name Signed-off-by: Anubhav Gupta Signed-off-by: Anubhav Gupta * Per 307 fail on severity counters (#831) * feat: fail on exceeding severity thresholds (#830) - Add support for severity counters - Add support for CLI flags that set severity thresholds - Terminate Kubescape with an exit code 1 if scan results exceed the severity thresholds * Update opa-utils pkg version Co-authored-by: Vlad Klokun * Fix merge conflict * typo in .gitignore file (#833) * remove unsupported installation method * fixed welcome message * fixed merge * fixed attack tracks loading logic Signed-off-by: Anubhav Gupta Co-authored-by: Moshe-Rappaport-CA Co-authored-by: Moshe Rappaport <89577611+Moshe-Rappaport-CA@users.noreply.github.com> Co-authored-by: Om Raut <33827410+om2137@users.noreply.github.com> Co-authored-by: Kamal Nayan <95926324+legendarykamal@users.noreply.github.com> Co-authored-by: Vlad Klokun Co-authored-by: Chirag Arora <84070677+Chirag8023@users.noreply.github.com> Co-authored-by: shm12 Co-authored-by: Amir Malka Co-authored-by: Krishna Agarwal Co-authored-by: Saswata Senapati <74651639+saswat16@users.noreply.github.com> Co-authored-by: Rahul Singh <110548934+rahuldhirendersingh@users.noreply.github.com> Co-authored-by: deepuyadav004 Co-authored-by: kartik <97971066+kartikgajjar7@users.noreply.github.com> Co-authored-by: Rounak-28 <95576871+Rounak-28@users.noreply.github.com> Co-authored-by: pwnb0y Co-authored-by: Ben Hirschberg <59160382+slashben@users.noreply.github.com> Co-authored-by: Saptarshi Sarkar Co-authored-by: Rahul Surwade <93492791+RahulSurwade08@users.noreply.github.com> Co-authored-by: Suhas Gumma <43647369+suhasgumma@users.noreply.github.com> Co-authored-by: TarangVerma <90996971+TarangVerma@users.noreply.github.com> Co-authored-by: avikittu <65793296+avikittu@users.noreply.github.com> Co-authored-by: satyam kale Co-authored-by: Aditya Pratap Singh Co-authored-by: Ashray Shetty Co-authored-by: Anubhav Gupta Co-authored-by: Meyazhagan --- .github/workflows/community.yml | 6 +- .gitignore | 2 +- README.md | 4 +- cmd/download/download.go | 2 +- cmd/scan/control.go | 2 + cmd/scan/framework.go | 30 ++++ cmd/scan/scan.go | 11 +- cmd/submit/rbac.go | 8 +- cmd/submit/results.go | 2 +- core/cautils/customerloader.go | 34 +++- core/cautils/customerloader_test.go | 31 ++++ core/cautils/datastructures.go | 3 +- core/cautils/getter/downloadreleasedpolicy.go | 9 + core/cautils/getter/getpolicies.go | 5 + core/cautils/getter/kscloudapi.go | 15 ++ core/cautils/getter/kscloudapiutils.go | 11 ++ core/cautils/scaninfo.go | 65 ++++--- core/core/cachedconfig.go | 6 +- core/core/delete.go | 2 +- core/core/download.go | 8 +- core/core/initutils.go | 20 ++- core/core/list.go | 6 +- core/core/scan.go | 18 +- core/core/submit.go | 2 +- core/pkg/opaprocessor/processorhandler.go | 6 +- .../pkg/opaprocessor/processorhandlerutils.go | 22 +-- .../prioritizationhandler.go | 102 ++++++++--- .../prioritizationhandler_test.go | 89 ++++++++-- .../resultshandling/printer/v2/htmlprinter.go | 2 +- core/pkg/resultshandling/printer/v2/junit.go | 164 ++++++++++-------- .../printer/v2/prettyprinter.go | 18 +- .../printer/v2/resourcetable.go | 12 +- .../reporter/v2/reporteventreceiver.go | 31 ++-- go.mod | 3 +- go.sum | 6 +- httphandler/go.mod | 4 +- httphandler/go.sum | 7 +- 37 files changed, 539 insertions(+), 229 deletions(-) diff --git a/.github/workflows/community.yml b/.github/workflows/community.yml index 1d88e1bc..65506019 100644 --- a/.github/workflows/community.yml +++ b/.github/workflows/community.yml @@ -17,6 +17,6 @@ jobs: - uses: EddieHubCommunity/gh-action-community/src/welcome@main with: github-token: ${{ secrets.GITHUB_TOKEN }} - issue-message: '

Hey, Welcome to this repo, Congratulations on opening your issue. Keep Contributing to Kubescape

' - pr-message: '

Hey, Welcome to this repo, Congratulations on opening your Pull Request. Keep Contributing to Kubescape

' - footer: '

We''ll try to review and add you work as soon as possible and a maintainer will get back to you soon!

' + issue-message: '

Hi! Welcome to Kubescape. Thank you for taking the time and reporting an issue

' + pr-message: '

Hi! Welcome to Kubescape. Thank you for taking the time and contributing to the open source community

' + footer: '

We will try to review as soon as possible!

' diff --git a/.gitignore b/.gitignore index 1d505096..676bc584 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ *.vs* *kubescape* *debug* -*vender* +*vendor* *.pyc* .idea .history diff --git a/README.md b/README.md index 14e6a45b..83b358f1 100644 --- a/README.md +++ b/README.md @@ -30,14 +30,14 @@ Kubescape integrates natively with other DevOps tools, including Jenkins, Circle curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash ``` +*OR:* + [Install on windows](#install-on-windows) [Install on macOS](#install-on-macos) [Install on NixOS or Linux/macOS via nix](#install-on-nixos-or-with-nix-community) -[Install using Go](#install-using-go) - ## Run: ```sh kubescape scan --submit --enable-host-scan --verbose diff --git a/cmd/download/download.go b/cmd/download/download.go index 89106eed..dbb00d69 100644 --- a/cmd/download/download.go +++ b/cmd/download/download.go @@ -17,7 +17,7 @@ var ( downloadExample = ` # Download all artifacts and save them in the default path (~/.kubescape) kubescape download artifacts - download + # Download all artifacts and save them in /tmp path kubescape download artifacts --output /tmp diff --git a/cmd/scan/control.go b/cmd/scan/control.go index 59fd1867..3aa056b1 100644 --- a/cmd/scan/control.go +++ b/cmd/scan/control.go @@ -101,6 +101,8 @@ func getControlCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comman if results.GetRiskScore() > float32(scanInfo.FailThreshold) { logger.L().Fatal("scan risk-score is above permitted threshold", helpers.String("risk-score", fmt.Sprintf("%.2f", results.GetRiskScore())), helpers.String("fail-threshold", fmt.Sprintf("%.2f", scanInfo.FailThreshold))) } + + enforceSeverityThresholds(&results.GetResults().SummaryDetails.SeverityCounters, scanInfo) return nil }, } diff --git a/cmd/scan/framework.go b/cmd/scan/framework.go index 602c52ca..30de2e0f 100644 --- a/cmd/scan/framework.go +++ b/cmd/scan/framework.go @@ -7,6 +7,7 @@ import ( "strings" apisv1 "github.com/kubescape/opa-utils/httpserver/apis/v1" + "github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary" logger "github.com/kubescape/go-logger" "github.com/kubescape/go-logger/helpers" @@ -113,11 +114,40 @@ func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comm if results.GetRiskScore() > float32(scanInfo.FailThreshold) { logger.L().Fatal("scan risk-score is above permitted threshold", helpers.String("risk-score", fmt.Sprintf("%.2f", results.GetRiskScore())), helpers.String("fail-threshold", fmt.Sprintf("%.2f", scanInfo.FailThreshold))) } + + enforceSeverityThresholds(&results.GetData().Report.SummaryDetails.SeverityCounters, scanInfo) return nil }, } } +// enforceSeverityThresholds ensures that the scan results are below defined severity thresholds +// +// The function forces the application to terminate with an exit code 1 if there are more resources with failed controls of a given severity than permitted +func enforceSeverityThresholds(severityCounters reportsummary.ISeverityCounters, scanInfo *cautils.ScanInfo) { + failedCritical := severityCounters.NumberOfResourcesWithCriticalSeverity() + failedHigh := severityCounters.NumberOfResourcesWithHighSeverity() + failedMedium := severityCounters.NumberOfResourcesWithMediumSeverity() + failedLow := severityCounters.NumberOfResourcesWithLowSeverity() + + criticalExceeded := failedCritical > scanInfo.FailThresholdCritical + highExceeded := failedHigh > scanInfo.FailThresholdHigh + mediumExceeded := failedMedium > scanInfo.FailThresholdMedium + lowExceeded := failedLow > scanInfo.FailThresholdLow + + resourceThresholdsExceeded := criticalExceeded || highExceeded || mediumExceeded || lowExceeded + + if resourceThresholdsExceeded { + logger.L().Fatal( + "There were failed controls that exceed permitted severity thresholds", + helpers.String("critical", fmt.Sprintf("got: %d, permitted: %d", failedCritical, scanInfo.FailThresholdCritical)), + helpers.String("high", fmt.Sprintf("got: %d, permitted: %d", failedHigh, scanInfo.FailThresholdHigh)), + helpers.String("medium", fmt.Sprintf("got: %d, permitted: %d", failedMedium, scanInfo.FailThresholdMedium)), + helpers.String("low", fmt.Sprintf("got: %d, permitted: %d", failedLow, scanInfo.FailThresholdLow)), + ) + } +} + func flagValidationFramework(scanInfo *cautils.ScanInfo) error { if scanInfo.Submit && scanInfo.Local { return fmt.Errorf("you can use `keep-local` or `submit`, but not both") diff --git a/cmd/scan/scan.go b/cmd/scan/scan.go index f4f937b9..10fdd001 100644 --- a/cmd/scan/scan.go +++ b/cmd/scan/scan.go @@ -2,6 +2,7 @@ package scan import ( "fmt" + "math" "github.com/kubescape/k8s-interface/k8sinterface" "github.com/kubescape/kubescape/v2/core/cautils" @@ -71,7 +72,14 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command { scanCmd.PersistentFlags().StringVar(&scanInfo.UseExceptions, "exceptions", "", "Path to an exceptions obj. If not set will download exceptions from ARMO management portal") scanCmd.PersistentFlags().StringVar(&scanInfo.UseArtifactsFrom, "use-artifacts-from", "", "Load artifacts from local directory. If not used will download them") scanCmd.PersistentFlags().StringVarP(&scanInfo.ExcludedNamespaces, "exclude-namespaces", "e", "", "Namespaces to exclude from scanning. Recommended: kube-system,kube-public") + scanCmd.PersistentFlags().Float32VarP(&scanInfo.FailThreshold, "fail-threshold", "t", 100, "Failure threshold is the percent above which the command fails and returns exit code 1") + + scanCmd.PersistentFlags().IntVar(&scanInfo.FailThresholdCritical, "threshold-critical", math.MaxInt, "Critical threshold is the amount of resources that have critical failed controls above which the command fails and returns exit code 1") + scanCmd.PersistentFlags().IntVar(&scanInfo.FailThresholdHigh, "threshold-high", math.MaxInt, "The amount of resources that have failed controls with High severity above which the command fails and returns exit code 1") + scanCmd.PersistentFlags().IntVar(&scanInfo.FailThresholdMedium, "threshold-medium", math.MaxInt, "The amount of resources that have failed controls with Medium severity above which the command fails and returns exit code 1") + scanCmd.PersistentFlags().IntVar(&scanInfo.FailThresholdLow, "threshold-low", math.MaxInt, "The amount of resources that have failed controls with Low severity above which the command fails and returns exit code 1") + scanCmd.PersistentFlags().StringVarP(&scanInfo.Format, "format", "f", "pretty-printer", `Output format. Supported formats: "pretty-printer", "json", "junit", "prometheus", "pdf", "html"`) scanCmd.PersistentFlags().StringVar(&scanInfo.IncludeNamespaces, "include-namespaces", "", "scan specific namespaces. e.g: --include-namespaces ns-a,ns-b") scanCmd.PersistentFlags().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") @@ -82,7 +90,8 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command { scanCmd.PersistentFlags().StringSliceVar(&scanInfo.UseFrom, "use-from", nil, "Load local policy object from specified path. If not used will download latest") scanCmd.PersistentFlags().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") scanCmd.PersistentFlags().StringVar(&scanInfo.HostSensorYamlPath, "host-scan-yaml", "", "Override default host scanner DaemonSet. Use this flag cautiously") - scanCmd.PersistentFlags().StringVar(&scanInfo.FormatVersion, "format-version", "v1", "Output object can be differnet between versions, this is for maintaining backward and forward compatibility. Supported:'v1'/'v2'") + scanCmd.PersistentFlags().StringVar(&scanInfo.FormatVersion, "format-version", "v1", "Output object can be different between versions, this is for maintaining backward and forward compatibility. Supported:'v1'/'v2'") + scanCmd.PersistentFlags().StringVar(&scanInfo.CustomClusterName, "cluster-name", "", "Set the custom name of the cluster. Not same as the kube-context flag") // Deprecated flags - remove 1.May.2022 scanCmd.PersistentFlags().BoolVarP(&scanInfo.Silent, "silent", "s", false, "Silent progress messages") diff --git a/cmd/submit/rbac.go b/cmd/submit/rbac.go index b0f33a14..d18af31c 100644 --- a/cmd/submit/rbac.go +++ b/cmd/submit/rbac.go @@ -40,7 +40,7 @@ func getRBACCmd(ks meta.IKubescape, submitInfo *v1.Submit) *cobra.Command { k8s := k8sinterface.NewKubernetesApi() // get config - clusterConfig := getTenantConfig(&submitInfo.Credentials, "", k8s) + clusterConfig := getTenantConfig(&submitInfo.Credentials, "", "", k8s) if err := clusterConfig.SetTenant(); err != nil { logger.L().Error("failed setting account ID", helpers.Error(err)) } @@ -77,9 +77,9 @@ func getKubernetesApi() *k8sinterface.KubernetesApi { } return k8sinterface.NewKubernetesApi() } -func getTenantConfig(credentials *cautils.Credentials, clusterName string, k8s *k8sinterface.KubernetesApi) cautils.ITenantConfig { +func getTenantConfig(credentials *cautils.Credentials, clusterName string, customClusterName string, k8s *k8sinterface.KubernetesApi) cautils.ITenantConfig { if !k8sinterface.IsConnectedToCluster() || k8s == nil { - return cautils.NewLocalConfig(getter.GetKSCloudAPIConnector(), credentials, clusterName) + return cautils.NewLocalConfig(getter.GetKSCloudAPIConnector(), credentials, clusterName, customClusterName) } - return cautils.NewClusterConfig(k8s, getter.GetKSCloudAPIConnector(), credentials, clusterName) + return cautils.NewClusterConfig(k8s, getter.GetKSCloudAPIConnector(), credentials, clusterName, customClusterName) } diff --git a/cmd/submit/results.go b/cmd/submit/results.go index c8aa2430..15102ef8 100644 --- a/cmd/submit/results.go +++ b/cmd/submit/results.go @@ -61,7 +61,7 @@ func getResultsCmd(ks meta.IKubescape, submitInfo *v1.Submit) *cobra.Command { k8s := getKubernetesApi() // get config - clusterConfig := getTenantConfig(&submitInfo.Credentials, "", k8s) + clusterConfig := getTenantConfig(&submitInfo.Credentials, "", "", k8s) if err := clusterConfig.SetTenant(); err != nil { logger.L().Error("failed setting account ID", helpers.Error(err)) } diff --git a/core/cautils/customerloader.go b/core/cautils/customerloader.go index 690f0692..44df78cb 100644 --- a/core/cautils/customerloader.go +++ b/core/cautils/customerloader.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "os" + "regexp" "strings" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -90,7 +91,7 @@ type LocalConfig struct { } func NewLocalConfig( - backendAPI getter.IBackend, credentials *Credentials, clusterName string) *LocalConfig { + backendAPI getter.IBackend, credentials *Credentials, clusterName string, customClusterName string) *LocalConfig { lc := &LocalConfig{ backendAPI: backendAPI, @@ -103,7 +104,10 @@ func NewLocalConfig( updateCredentials(lc.configObj, credentials) - if clusterName != "" { + // If a custom cluster name is provided then set that name, else use the cluster's original name + if customClusterName != "" { + lc.configObj.ClusterName = AdoptCustomClusterName(customClusterName) + } else if clusterName != "" { lc.configObj.ClusterName = AdoptClusterName(clusterName) // override config clusterName } @@ -189,7 +193,7 @@ type ClusterConfig struct { configMapNamespace string } -func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBackend, credentials *Credentials, clusterName string) *ClusterConfig { +func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBackend, credentials *Credentials, clusterName string, customClusterName string) *ClusterConfig { // var configObj *ConfigObj c := &ClusterConfig{ k8s: k8s, @@ -210,7 +214,10 @@ func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBacken } updateCredentials(c.configObj, credentials) - if clusterName != "" { + // If a custom cluster name is provided then set that name, else use the cluster's original name + if customClusterName != "" { + c.configObj.ClusterName = AdoptCustomClusterName(customClusterName) + } else if clusterName != "" { c.configObj.ClusterName = AdoptClusterName(clusterName) // override config clusterName } @@ -467,8 +474,25 @@ func DeleteConfigFile() error { return os.Remove(ConfigFileFullPath()) } +// To check if the custom cluster name is valid: +func AdoptCustomClusterName(customClusterName string) string { + is_alphanumeric := regexp.MustCompile(`^[a-zA-Z0-9]*$`).MatchString(customClusterName) + + // Check it does not contain special-characters + if is_alphanumeric == false { + logger.L().Fatal("custom cluster name cannot contain special characters") + } else if len(customClusterName) >= 256 { // Check it contains less than 256 characters + logger.L().Fatal("custom cluster name cannot contain more than 255 characters") + } + return customClusterName +} + func AdoptClusterName(clusterName string) string { - return strings.ReplaceAll(clusterName, "/", "-") + re, err := regexp.Compile(`[^\w]+`) + if err != nil { + return clusterName + } + return re.ReplaceAllString(clusterName, "-") } func getConfigMapName() string { diff --git a/core/cautils/customerloader_test.go b/core/cautils/customerloader_test.go index 3a17106f..8a7f1ce5 100644 --- a/core/cautils/customerloader_test.go +++ b/core/cautils/customerloader_test.go @@ -191,3 +191,34 @@ func TestLoadConfigFromData(t *testing.T) { } } + +func TestAdoptClusterName(t *testing.T) { + tests := []struct { + name string + clusterName string + want string + }{ + { + name: "replace 1", + clusterName: "my-name__is--ks", + want: "my-name__is-ks", + }, + { + name: "replace 2", + clusterName: "my-name1", + want: "my-name1", + }, + { + name: "replace 3", + clusterName: "my:name", + want: "my-name", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := AdoptClusterName(tt.clusterName); got != tt.want { + t.Errorf("AdoptClusterName() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/core/cautils/datastructures.go b/core/cautils/datastructures.go index 733ca8d7..51c83843 100644 --- a/core/cautils/datastructures.go +++ b/core/cautils/datastructures.go @@ -17,6 +17,7 @@ type KSResources map[string][]string type OPASessionObj struct { K8SResources *K8SResources // input k8s objects ArmoResource *KSResources // input ARMO objects + AllPolicies *Policies // list of all frameworks Policies []reporthandling.Framework // list of frameworks to scan AllResources map[string]workloadinterface.IMetadata // all scanned resources, map[] ResourcesResult map[string]resourcesresults.Result // resources scan results, map[] @@ -24,7 +25,7 @@ type OPASessionObj struct { ResourcesPrioritized map[string]prioritization.PrioritizedResource // resources prioritization information, map[] Report *reporthandlingv2.PostureReport // scan results v2 - Remove Exceptions []armotypes.PostureExceptionPolicy // list of exceptions to apply on scan results - RegoInputData RegoInputData // input passed to rgo for scanning. map[][] + RegoInputData RegoInputData // input passed to rego for scanning. map[][] Metadata *reporthandlingv2.Metadata InfoMap map[string]apis.StatusInfo // Map errors of resources to StatusInfo ResourceToControlsMap map[string][]string // map[] = [] diff --git a/core/cautils/getter/downloadreleasedpolicy.go b/core/cautils/getter/downloadreleasedpolicy.go index b1289add..a28c3fc2 100644 --- a/core/cautils/getter/downloadreleasedpolicy.go +++ b/core/cautils/getter/downloadreleasedpolicy.go @@ -5,6 +5,7 @@ import ( "github.com/kubescape/opa-utils/gitregostore" "github.com/kubescape/opa-utils/reporthandling" + "github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1" ) // ======================================================================================================================= @@ -70,6 +71,14 @@ func (drp *DownloadReleasedPolicy) GetControlsInputs(clusterName string) (map[st return defaultConfigInputs.Settings.PostureControlInputs, err } +func (drp *DownloadReleasedPolicy) GetAttackTracks() ([]v1alpha1.AttackTrack, error) { + attackTracks, err := drp.gs.GetAttackTracks() + if err != nil { + return nil, err + } + return attackTracks, err +} + func (drp *DownloadReleasedPolicy) SetRegoObjects() error { fwNames, err := drp.gs.GetOPAFrameworksNamesList() if len(fwNames) != 0 && err == nil { diff --git a/core/cautils/getter/getpolicies.go b/core/cautils/getter/getpolicies.go index 679ad92a..6dacca80 100644 --- a/core/cautils/getter/getpolicies.go +++ b/core/cautils/getter/getpolicies.go @@ -3,6 +3,7 @@ package getter import ( "github.com/armosec/armoapi-go/armotypes" "github.com/kubescape/opa-utils/reporthandling" + "github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1" ) // supported listing @@ -38,3 +39,7 @@ type IBackend interface { type IControlsInputsGetter interface { GetControlsInputs(clusterName string) (map[string][]string, error) } + +type IAttackTracksGetter interface { + GetAttackTracks() ([]v1alpha1.AttackTrack, error) +} diff --git a/core/cautils/getter/kscloudapi.go b/core/cautils/getter/kscloudapi.go index 437ffa06..3bf04179 100644 --- a/core/cautils/getter/kscloudapi.go +++ b/core/cautils/getter/kscloudapi.go @@ -13,6 +13,7 @@ import ( logger "github.com/kubescape/go-logger" "github.com/kubescape/go-logger/helpers" "github.com/kubescape/opa-utils/reporthandling" + "github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1" ) var ( @@ -147,6 +148,20 @@ func (api *KSCloudAPI) SetAccountID(accountID string) { api.accountID = accountI func (api *KSCloudAPI) SetClientID(clientID string) { api.clientID = clientID } func (api *KSCloudAPI) SetSecretKey(secretKey string) { api.secretKey = secretKey } +func (api *KSCloudAPI) GetAttackTracks() ([]v1alpha1.AttackTrack, error) { + respStr, err := api.Get(api.getAttackTracksURL(), nil) + if err != nil { + return nil, nil + } + + attackTracks := []v1alpha1.AttackTrack{} + if err = JSONDecoder(respStr).Decode(&attackTracks); err != nil { + return nil, err + } + + return attackTracks, err +} + func (api *KSCloudAPI) GetFramework(name string) (*reporthandling.Framework, error) { respStr, err := api.Get(api.getFrameworkURL(name), nil) if err != nil { diff --git a/core/cautils/getter/kscloudapiutils.go b/core/cautils/getter/kscloudapiutils.go index 4aae02ab..254d40ee 100644 --- a/core/cautils/getter/kscloudapiutils.go +++ b/core/cautils/getter/kscloudapiutils.go @@ -28,6 +28,17 @@ func (api *KSCloudAPI) getFrameworkURL(frameworkName string) string { return u.String() } +func (api *KSCloudAPI) getAttackTracksURL() string { + u := url.URL{} + u.Scheme, u.Host = parseHost(api.GetApiURL()) + u.Path = "api/v1/attackTracks" + q := u.Query() + q.Add("customerGUID", api.getCustomerGUIDFallBack()) + u.RawQuery = q.Encode() + + return u.String() +} + func (api *KSCloudAPI) getListFrameworkURL() string { u := url.URL{} u.Scheme, u.Host = parseHost(api.GetApiURL()) diff --git a/core/cautils/scaninfo.go b/core/cautils/scaninfo.go index 06364b8c..059ff3ef 100644 --- a/core/cautils/scaninfo.go +++ b/core/cautils/scaninfo.go @@ -100,38 +100,44 @@ type PolicyIdentifier struct { } type ScanInfo struct { - Getters // TODO - remove from object - PolicyIdentifier []PolicyIdentifier // TODO - remove from object - UseExceptions string // Load file with exceptions configuration - ControlsInputs string // Load file with inputs for controls - UseFrom []string // Load framework from local file (instead of download). Use when running offline - UseDefault bool // Load framework from cached file (instead of download). Use when running offline - UseArtifactsFrom string // Load artifacts from local path. Use when running offline - VerboseMode bool // Display all of the input resources and not only failed resources - View string // Display all of the input resources and not only failed resources - Format string // Format results (table, json, junit ...) - Output string // Store results in an output file, Output file name - FormatVersion string // Output object can be different between versions, this is for testing and backward compatibility - ExcludedNamespaces string // used for host scanner namespace - IncludeNamespaces string // - InputPatterns []string // Yaml files input patterns - Silent bool // Silent mode - Do not print progress logs - FailThreshold float32 // Failure score threshold - Submit bool // Submit results to Kubescape Cloud BE - ScanID string // Report id of the current scan - HostSensorEnabled BoolPtrFlag // Deploy Kubescape K8s host scanner to collect data from certain controls - HostSensorYamlPath string // Path to hostsensor file - Local bool // Do not submit results - Credentials Credentials // account ID - KubeContext string // context name - FrameworkScan bool // false if scanning control - ScanAll bool // true if scan all frameworks + Getters // TODO - remove from object + PolicyIdentifier []PolicyIdentifier // TODO - remove from object + UseExceptions string // Load file with exceptions configuration + ControlsInputs string // Load file with inputs for controls + UseFrom []string // Load framework from local file (instead of download). Use when running offline + UseDefault bool // Load framework from cached file (instead of download). Use when running offline + UseArtifactsFrom string // Load artifacts from local path. Use when running offline + VerboseMode bool // Display all of the input resources and not only failed resources + View string // Display all of the input resources and not only failed resources + Format string // Format results (table, json, junit ...) + Output string // Store results in an output file, Output file name + FormatVersion string // Output object can be differnet between versions, this is for testing and backward compatibility + CustomClusterName string // Set the custom name of the cluster + ExcludedNamespaces string // used for host scanner namespace + IncludeNamespaces string // + InputPatterns []string // Yaml files input patterns + Silent bool // Silent mode - Do not print progress logs + FailThreshold float32 // Failure score threshold + FailThresholdCritical int // Threshold for failing based on the amount of resources with Critical severity failed controls + FailThresholdHigh int // Threshold for failing based on the amount of resources with High severity failed controls + FailThresholdMedium int // Threshold for failing based on the amount of resources with Medium severity failed controls + FailThresholdLow int // Threshold for failing based on the amount of resources with Low severity failed controls + Submit bool // Submit results to Kubescape Cloud BE + ScanID string // Report id of the current scan + HostSensorEnabled BoolPtrFlag // Deploy Kubescape K8s host scanner to collect data from certain controls + HostSensorYamlPath string // Path to hostsensor file + Local bool // Do not submit results + Credentials Credentials // account ID + KubeContext string // context name + FrameworkScan bool // false if scanning control + ScanAll bool // true if scan all frameworks } type Getters struct { ExceptionsGetter getter.IExceptionsGetter ControlsInputsGetter getter.IControlsInputsGetter PolicyGetter getter.IPolicyGetter + AttackTracksGetter getter.IAttackTracksGetter } func (scanInfo *ScanInfo) Init() { @@ -205,13 +211,6 @@ func (scanInfo *ScanInfo) setOutputFile() { } } -// func (scanInfo *ScanInfo) GetScanningEnvironment() string { -// if len(scanInfo.InputPatterns) != 0 { -// return ScanLocalFiles -// } -// return ScanCluster -// } - func (scanInfo *ScanInfo) SetPolicyIdentifiers(policies []string, kind apisv1.NotificationPolicyKind) { for _, policy := range policies { if !scanInfo.contains(policy) { diff --git a/core/core/cachedconfig.go b/core/core/cachedconfig.go index b575eb9c..94d057f1 100644 --- a/core/core/cachedconfig.go +++ b/core/core/cachedconfig.go @@ -8,7 +8,7 @@ import ( func (ks *Kubescape) SetCachedConfig(setConfig *metav1.SetConfig) error { - tenant := getTenantConfig(nil, "", getKubernetesApi()) + tenant := getTenantConfig(nil, "", "", getKubernetesApi()) if setConfig.Account != "" { tenant.GetConfigObj().AccountID = setConfig.Account @@ -25,13 +25,13 @@ func (ks *Kubescape) SetCachedConfig(setConfig *metav1.SetConfig) error { // View cached configurations func (ks *Kubescape) ViewCachedConfig(viewConfig *metav1.ViewConfig) error { - tenant := getTenantConfig(nil, "", getKubernetesApi()) // change k8sinterface + tenant := getTenantConfig(nil, "", "", getKubernetesApi()) // change k8sinterface fmt.Fprintf(viewConfig.Writer, "%s\n", tenant.GetConfigObj().Config()) return nil } func (ks *Kubescape) DeleteCachedConfig(deleteConfig *metav1.DeleteConfig) error { - tenant := getTenantConfig(nil, "", getKubernetesApi()) // change k8sinterface + tenant := getTenantConfig(nil, "", "", getKubernetesApi()) // change k8sinterface return tenant.DeleteCachedConfig() } diff --git a/core/core/delete.go b/core/core/delete.go index 0439db68..53fc774f 100644 --- a/core/core/delete.go +++ b/core/core/delete.go @@ -12,7 +12,7 @@ import ( func (ks *Kubescape) DeleteExceptions(delExceptions *v1.DeleteExceptions) error { // load cached config - getTenantConfig(&delExceptions.Credentials, "", getKubernetesApi()) + getTenantConfig(&delExceptions.Credentials, "", "", getKubernetesApi()) // login kubescape SaaS ksCloudAPI := getter.GetKSCloudAPIConnector() diff --git a/core/core/download.go b/core/core/download.go index d5760a65..3e71a100 100644 --- a/core/core/download.go +++ b/core/core/download.go @@ -80,7 +80,7 @@ func downloadArtifacts(downloadInfo *metav1.DownloadInfo) error { } func downloadConfigInputs(downloadInfo *metav1.DownloadInfo) error { - tenant := getTenantConfig(&downloadInfo.Credentials, "", getKubernetesApi()) + tenant := getTenantConfig(&downloadInfo.Credentials, "", "", getKubernetesApi()) controlsInputsGetter := getConfigInputsGetter(downloadInfo.Name, tenant.GetAccountID(), nil) controlInputs, err := controlsInputsGetter.GetControlsInputs(tenant.GetContextName()) @@ -104,7 +104,7 @@ func downloadConfigInputs(downloadInfo *metav1.DownloadInfo) error { func downloadExceptions(downloadInfo *metav1.DownloadInfo) error { var err error - tenant := getTenantConfig(&downloadInfo.Credentials, "", getKubernetesApi()) + tenant := getTenantConfig(&downloadInfo.Credentials, "", "", getKubernetesApi()) exceptionsGetter := getExceptionsGetter("") exceptions := []armotypes.PostureExceptionPolicy{} @@ -128,7 +128,7 @@ func downloadExceptions(downloadInfo *metav1.DownloadInfo) error { func downloadFramework(downloadInfo *metav1.DownloadInfo) error { - tenant := getTenantConfig(&downloadInfo.Credentials, "", getKubernetesApi()) + tenant := getTenantConfig(&downloadInfo.Credentials, "", "", getKubernetesApi()) g := getPolicyGetter(nil, tenant.GetTenantEmail(), true, nil) @@ -170,7 +170,7 @@ func downloadFramework(downloadInfo *metav1.DownloadInfo) error { func downloadControl(downloadInfo *metav1.DownloadInfo) error { - tenant := getTenantConfig(&downloadInfo.Credentials, "", getKubernetesApi()) + tenant := getTenantConfig(&downloadInfo.Credentials, "", "", getKubernetesApi()) g := getPolicyGetter(nil, tenant.GetTenantEmail(), false, nil) diff --git a/core/core/initutils.go b/core/core/initutils.go index bb145b1a..b46b925a 100644 --- a/core/core/initutils.go +++ b/core/core/initutils.go @@ -25,11 +25,11 @@ func getKubernetesApi() *k8sinterface.KubernetesApi { } return k8sinterface.NewKubernetesApi() } -func getTenantConfig(credentials *cautils.Credentials, clusterName string, k8s *k8sinterface.KubernetesApi) cautils.ITenantConfig { +func getTenantConfig(credentials *cautils.Credentials, clusterName string, customClusterName string, k8s *k8sinterface.KubernetesApi) cautils.ITenantConfig { if !k8sinterface.IsConnectedToCluster() || k8s == nil { - return cautils.NewLocalConfig(getter.GetKSCloudAPIConnector(), credentials, clusterName) + return cautils.NewLocalConfig(getter.GetKSCloudAPIConnector(), credentials, clusterName, customClusterName) } - return cautils.NewClusterConfig(k8s, getter.GetKSCloudAPIConnector(), credentials, clusterName) + return cautils.NewClusterConfig(k8s, getter.GetKSCloudAPIConnector(), credentials, clusterName, customClusterName) } func getExceptionsGetter(useExceptions string) getter.IExceptionsGetter { @@ -223,3 +223,17 @@ func listFrameworksNames(policyGetter getter.IPolicyGetter) []string { } return getter.NativeFrameworks } + +func getAttackTracksGetter(accountID string, downloadReleasedPolicy *getter.DownloadReleasedPolicy) getter.IAttackTracksGetter { + if accountID != "" { + g := getter.GetKSCloudAPIConnector() // download attack tracks from Kubescape Cloud backend + return g + } + if downloadReleasedPolicy == nil { + downloadReleasedPolicy = getter.NewDownloadReleasedPolicy() + } + if err := downloadReleasedPolicy.SetRegoObjects(); err != nil { + logger.L().Warning("failed to get attack tracks from github release, this may affect the scanning results", helpers.Error(err)) + } + return downloadReleasedPolicy +} diff --git a/core/core/list.go b/core/core/list.go index a0383bf3..d4dd9afc 100644 --- a/core/core/list.go +++ b/core/core/list.go @@ -44,14 +44,14 @@ func (ks *Kubescape) List(listPolicies *metav1.ListPolicies) error { } func listFrameworks(listPolicies *metav1.ListPolicies) ([]string, error) { - tenant := getTenantConfig(&listPolicies.Credentials, "", getKubernetesApi()) // change k8sinterface + tenant := getTenantConfig(&listPolicies.Credentials, "", "", getKubernetesApi()) // change k8sinterface g := getPolicyGetter(nil, tenant.GetTenantEmail(), true, nil) return listFrameworksNames(g), nil } func listControls(listPolicies *metav1.ListPolicies) ([]string, error) { - tenant := getTenantConfig(&listPolicies.Credentials, "", getKubernetesApi()) // change k8sinterface + tenant := getTenantConfig(&listPolicies.Credentials, "", "", getKubernetesApi()) // change k8sinterface g := getPolicyGetter(nil, tenant.GetTenantEmail(), false, nil) l := getter.ListName @@ -63,7 +63,7 @@ func listControls(listPolicies *metav1.ListPolicies) ([]string, error) { func listExceptions(listPolicies *metav1.ListPolicies) ([]string, error) { // load tenant metav1 - getTenantConfig(&listPolicies.Credentials, "", getKubernetesApi()) + getTenantConfig(&listPolicies.Credentials, "", "", getKubernetesApi()) var exceptionsNames []string ksCloudAPI := getExceptionsGetter("") diff --git a/core/core/scan.go b/core/core/scan.go index 46b065ba..49fa69ab 100644 --- a/core/core/scan.go +++ b/core/core/scan.go @@ -15,6 +15,7 @@ import ( "github.com/kubescape/kubescape/v2/core/pkg/opaprocessor" "github.com/kubescape/kubescape/v2/core/pkg/policyhandler" "github.com/kubescape/kubescape/v2/core/pkg/resourcehandler" + "github.com/kubescape/kubescape/v2/core/pkg/resourcesprioritization" "github.com/kubescape/kubescape/v2/core/pkg/resultshandling" "github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer" "github.com/kubescape/kubescape/v2/core/pkg/resultshandling/reporter" @@ -43,7 +44,7 @@ func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces { // ================== setup tenant object ====================================== - tenantConfig := getTenantConfig(&scanInfo.Credentials, scanInfo.KubeContext, k8s) + tenantConfig := getTenantConfig(&scanInfo.Credentials, scanInfo.KubeContext, scanInfo.CustomClusterName, k8s) // Set submit behavior AFTER loading tenant config setSubmitBehavior(scanInfo, tenantConfig) @@ -122,6 +123,7 @@ func (ks *Kubescape) Scan(scanInfo *cautils.ScanInfo) (*resultshandling.ResultsH scanInfo.Getters.PolicyGetter = getPolicyGetter(scanInfo.UseFrom, interfaces.tenantConfig.GetTenantEmail(), scanInfo.FrameworkScan, downloadReleasedPolicy) scanInfo.Getters.ControlsInputsGetter = getConfigInputsGetter(scanInfo.ControlsInputs, interfaces.tenantConfig.GetAccountID(), downloadReleasedPolicy) scanInfo.Getters.ExceptionsGetter = getExceptionsGetter(scanInfo.UseExceptions) + scanInfo.Getters.AttackTracksGetter = getAttackTracksGetter(interfaces.tenantConfig.GetAccountID(), downloadReleasedPolicy) // TODO - list supported frameworks/controls if scanInfo.ScanAll { @@ -152,15 +154,13 @@ func (ks *Kubescape) Scan(scanInfo *cautils.ScanInfo) (*resultshandling.ResultsH return resultsHandling, fmt.Errorf("%w", err) } - /* + // ======================== prioritization =================== - // ======================== prioritization =================== - priotizationHandler := resourcesprioritization.NewResourcesPrioritizationHandler(true) - if err := priotizationHandler.PrioritizeResources(scanData); err != nil { - return resultsHandling, fmt.Errorf("%w", err) - } - - */ + if priotizationHandler, err := resourcesprioritization.NewResourcesPrioritizationHandler(scanInfo.Getters.AttackTracksGetter); err != nil { + logger.L().Warning("failed to get attack tracks, this may affect the scanning results", helpers.Error(err)) + } else if err := priotizationHandler.PrioritizeResources(scanData); err != nil { + return resultsHandling, fmt.Errorf("%w", err) + } // ========================= results handling ===================== resultsHandling.SetData(scanData) diff --git a/core/core/submit.go b/core/core/submit.go index 115fc00f..13258f43 100644 --- a/core/core/submit.go +++ b/core/core/submit.go @@ -39,7 +39,7 @@ func (ks *Kubescape) SubmitExceptions(credentials *cautils.Credentials, excPath logger.L().Info("submitting exceptions", helpers.String("path", excPath)) // load cached config - tenantConfig := getTenantConfig(credentials, "", getKubernetesApi()) + tenantConfig := getTenantConfig(credentials, "", "", getKubernetesApi()) if err := tenantConfig.SetTenant(); err != nil { logger.L().Error("failed setting account ID", helpers.Error(err)) } diff --git a/core/pkg/opaprocessor/processorhandler.go b/core/pkg/opaprocessor/processorhandler.go index 7d9ddab4..8f2e32a6 100644 --- a/core/pkg/opaprocessor/processorhandler.go +++ b/core/pkg/opaprocessor/processorhandler.go @@ -43,12 +43,12 @@ func NewOPAProcessor(sessionObj *cautils.OPASessionObj, regoDependenciesData *re } func (opap *OPAProcessor) ProcessRulesListenner() error { - policies := ConvertFrameworksToPolicies(opap.Policies, cautils.BuildNumber) + opap.OPASessionObj.AllPolicies = ConvertFrameworksToPolicies(opap.Policies, cautils.BuildNumber) - ConvertFrameworksToSummaryDetails(&opap.Report.SummaryDetails, opap.Policies, policies) + ConvertFrameworksToSummaryDetails(&opap.Report.SummaryDetails, opap.Policies, opap.OPASessionObj.AllPolicies) // process - if err := opap.Process(policies); err != nil { + if err := opap.Process(opap.OPASessionObj.AllPolicies); err != nil { logger.L().Error(err.Error()) // Return error? } diff --git a/core/pkg/opaprocessor/processorhandlerutils.go b/core/pkg/opaprocessor/processorhandlerutils.go index ef796ae8..b468e668 100644 --- a/core/pkg/opaprocessor/processorhandlerutils.go +++ b/core/pkg/opaprocessor/processorhandlerutils.go @@ -12,12 +12,12 @@ import ( resources "github.com/kubescape/opa-utils/resources" ) -// updateResults update the results objects and report objects. This is a critical function - DO NOT CHANGE -/* - - remove sensible data - - adding exceptions - - summarize results -*/ +// updateResults updates the results objects and report objects. This is a critical function - DO NOT CHANGE +// +// The function: +// - removes sensible data +// - adds exceptions +// - summarizes results func (opap *OPAProcessor) updateResults() { // remove data from all objects @@ -49,16 +49,6 @@ func (opap *OPAProcessor) updateResults() { // map control to error controlToInfoMap := mapControlToInfo(opap.ResourceToControlsMap, opap.InfoMap, opap.Report.SummaryDetails.Controls) opap.Report.SummaryDetails.InitResourcesSummary(controlToInfoMap) - // for f := range opap.PostureReport.FrameworkReports { - // // set exceptions - // exceptions.SetFrameworkExceptions(&opap.PostureReport.FrameworkReports[f], opap.Exceptions, cautils.ClusterName) - - // // set counters - // reporthandling.SetUniqueResourcesCounter(&opap.PostureReport.FrameworkReports[f]) - - // // set default score - // // reporthandling.SetDefaultScore(&opap.PostureReport.FrameworkReports[f]) - // } } func mapControlToInfo(mapResourceToControls map[string][]string, infoMap map[string]apis.StatusInfo, controlSummary reportsummary.ControlSummaries) map[string]apis.StatusInfo { diff --git a/core/pkg/resourcesprioritization/prioritizationhandler.go b/core/pkg/resourcesprioritization/prioritizationhandler.go index 3441ad7f..ee41941d 100644 --- a/core/pkg/resourcesprioritization/prioritizationhandler.go +++ b/core/pkg/resourcesprioritization/prioritizationhandler.go @@ -5,21 +5,52 @@ import ( "github.com/kubescape/k8s-interface/workloadinterface" "github.com/kubescape/kubescape/v2/core/cautils" - "github.com/kubescape/opa-utils/reporthandling/apis" + "github.com/kubescape/kubescape/v2/core/cautils/getter" + "github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1" "github.com/kubescape/opa-utils/reporthandling/results/v1/prioritization" ) type ResourcesPrioritizationHandler struct { - skipZeroScores bool + attackTracks []v1alpha1.IAttackTrack } -func NewResourcesPrioritizationHandler(skipZeroScore bool) *ResourcesPrioritizationHandler { - return &ResourcesPrioritizationHandler{ - skipZeroScores: skipZeroScore, +func NewResourcesPrioritizationHandler(attackTracksGetter getter.IAttackTracksGetter) (*ResourcesPrioritizationHandler, error) { + handler := &ResourcesPrioritizationHandler{ + attackTracks: make([]v1alpha1.IAttackTrack, 0), } + + if tracks, err := attackTracksGetter.GetAttackTracks(); err != nil { + return nil, err + } else { + for _, attackTrack := range tracks { + if !attackTrack.IsValid() { + return nil, fmt.Errorf("invalid attack track: %s", attackTrack.GetName()) + } + + t := attackTrack + handler.attackTracks = append(handler.attackTracks, &t) + } + } + + if len(handler.attackTracks) == 0 { + return nil, fmt.Errorf("expected to find at least one attack track") + } + + return handler, nil } func (handler *ResourcesPrioritizationHandler) PrioritizeResources(sessionObj *cautils.OPASessionObj) error { + if sessionObj.AllPolicies == nil { + return fmt.Errorf("expected to find policies map") + } else if len(sessionObj.AllPolicies.Controls) == 0 { + return fmt.Errorf("expected to find controls in policies map") + } + allControls := make(map[string]v1alpha1.IAttackTrackControl, len(sessionObj.AllPolicies.Controls)) + for id := range sessionObj.AllPolicies.Controls { + ctrl := sessionObj.AllPolicies.Controls[id] + allControls[id] = &ctrl + } + for resourceId, result := range sessionObj.ResourcesResult { resourcePriorityVector := []prioritization.ControlsVector{} resource, exist := sessionObj.AllResources[resourceId] @@ -30,31 +61,48 @@ func (handler *ResourcesPrioritizationHandler) PrioritizeResources(sessionObj *c workload := workloadinterface.NewWorkloadObj(resource.GetObject()) if workload != nil && handler.isSupportedKind(workload) { - for _, resourceAssociatedControl := range result.ListControls() { - if !resourceAssociatedControl.GetStatus(nil).IsFailed() { - continue + // build a map of attack track categories to a list of failed controls for the specific resource + failedControls := result.ListControlsIDs(nil).Failed() + if len(failedControls) > 0 { + + controlsLookup := v1alpha1.NewAttackTrackControlsLookup(handler.attackTracks, failedControls, allControls) + replicaCount := workload.GetReplicas() + + for _, attackTrack := range handler.attackTracks { + if !controlsLookup.HasAssociatedControls(attackTrack.GetName()) { + continue + } + + // Load the failed controls into the attack track + allPathsHandler := v1alpha1.NewAttackTrackAllPathsHandler(attackTrack, &controlsLookup) + + // Calculate all the paths for the attack track + allAttackPaths := allPathsHandler.CalculateAllPaths() + + // Create priority vectors from every attack path + controlsVectors := prioritization.ControlsVectorFromAttackTrackPaths(attackTrack, allAttackPaths) + + // Calculate the score and severity for every priority vector, and add it to the resource priority vector + for _, controlsVector := range controlsVectors { + if score, err := controlsVector.CalculateScore(allControls, replicaCount); err == nil { + controlsVector.SetScore(score) + } else { + return err + } + + if severity, err := controlsVector.CalculateSeverity(allControls); err == nil { + controlsVector.SetSeverity(severity) + } else { + return err + } + + resourcePriorityVector = append(resourcePriorityVector, controlsVector) + } } - - controlSummary := sessionObj.Report.SummaryDetails.Controls.GetControl("ID", resourceAssociatedControl.ControlID) - if controlSummary == nil { - return fmt.Errorf("expected to find control id '%s' in summary details", resourceAssociatedControl.ControlID) - } - - controlScoreFactor := controlSummary.GetScoreFactor() - replicaCount := float64(workload.GetReplicas()) - - cVector := prioritization.NewControlsVector() - cVector.AddControl(prioritization.PriorityVectorControl{ - ControlID: resourceAssociatedControl.ControlID, - Category: "", - }) - - cVector.SetSeverity(apis.ControlSeverityToInt(controlScoreFactor)) - cVector.SetScore(float64(controlScoreFactor) + (replicaCount / 10)) - resourcePriorityVector = append(resourcePriorityVector, *cVector) } } + // Resource priority vector is ready, add it to the session object prioritizedResource := prioritization.PrioritizedResource{ ResourceID: resourceId, PriorityVector: resourcePriorityVector, @@ -63,7 +111,7 @@ func (handler *ResourcesPrioritizationHandler) PrioritizeResources(sessionObj *c prioritizedResource.SetSeverity(prioritizedResource.CalculateSeverity()) prioritizedResource.SetScore(prioritizedResource.CalculateScore()) - if handler.skipZeroScores && prioritizedResource.GetScore() == 0 { + if prioritizedResource.GetScore() == 0 { continue } diff --git a/core/pkg/resourcesprioritization/prioritizationhandler_test.go b/core/pkg/resourcesprioritization/prioritizationhandler_test.go index 36d1d8d9..3742bf97 100644 --- a/core/pkg/resourcesprioritization/prioritizationhandler_test.go +++ b/core/pkg/resourcesprioritization/prioritizationhandler_test.go @@ -4,19 +4,74 @@ import ( "fmt" "testing" + "github.com/armosec/armoapi-go/armotypes" "github.com/kubescape/k8s-interface/workloadinterface" "github.com/kubescape/kubescape/v2/core/cautils" + "github.com/kubescape/opa-utils/reporthandling" "github.com/kubescape/opa-utils/reporthandling/apis" + "github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1" "github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary" "github.com/kubescape/opa-utils/reporthandling/results/v1/resourcesresults" "github.com/stretchr/testify/assert" ) -func OPASessionObjMock(mockResults map[string]resourcesresults.Result, mockControlsSummary map[string]reportsummary.ControlSummary, mockAllResources map[string]workloadinterface.IMetadata) *cautils.OPASessionObj { +type AttackTracksGetterMock struct{} + +func (mock *AttackTracksGetterMock) GetAttackTracks() ([]v1alpha1.AttackTrack, error) { + mock_1 := v1alpha1.AttackTrackMock(v1alpha1.AttackTrackStep{ + Name: "A", + SubSteps: []v1alpha1.AttackTrackStep{ + { + Name: "B", + SubSteps: []v1alpha1.AttackTrackStep{ + { + Name: "C", + }, + { + Name: "D", + }, + }, + }, + { + Name: "E", + }, + }, + }) + + mock_2 := v1alpha1.AttackTrackMock(v1alpha1.AttackTrackStep{ + Name: "Z", + }) + mock_2.Metadata["name"] = "TestAttackTrack_2" + + return []v1alpha1.AttackTrack{*mock_1, *mock_2}, nil +} + +func ControlMock(id string, baseScore float32, tags, categories []string) reporthandling.Control { + return reporthandling.Control{ + ControlID: id, + BaseScore: baseScore, + PortalBase: armotypes.PortalBase{ + Attributes: map[string]interface{}{ + "controlTypeTags": tags, + "attackTracks": []reporthandling.AttackTrackCategories{ + { + AttackTrack: "TestAttackTrack", + Categories: categories, + }, + }, + }, + }, + } +} + +func OPASessionObjMock(allPoliciesControls map[string]reporthandling.Control, mockResults map[string]resourcesresults.Result, mockControlsSummary map[string]reportsummary.ControlSummary, mockAllResources map[string]workloadinterface.IMetadata) *cautils.OPASessionObj { mock := cautils.NewOPASessionObjMock() mock.Report.SummaryDetails.Controls = mockControlsSummary mock.ResourcesResult = mockResults mock.AllResources = mockAllResources + mock.AllPolicies = cautils.NewPolicies() + mock.AllPolicies.Controls = allPoliciesControls + return mock } @@ -41,9 +96,18 @@ func ResourceAssociatedControlMock(controlID string, status apis.ScanningStatus) } } +func TestNewResourcesPrioritizationHandler(t *testing.T) { + handler, err := NewResourcesPrioritizationHandler(&AttackTracksGetterMock{}) + assert.NoError(t, err) + assert.Len(t, handler.attackTracks, 2) + assert.Equal(t, handler.attackTracks[0].GetName(), "TestAttackTrack") + assert.Equal(t, handler.attackTracks[1].GetName(), "TestAttackTrack_2") +} + func TestResourcesPrioritizationHandler_PrioritizeResources(t *testing.T) { tests := []struct { name string + allPoliciesControls map[string]reporthandling.Control results map[string]resourcesresults.Result controls map[string]reportsummary.ControlSummary resources map[string]workloadinterface.IMetadata @@ -53,6 +117,11 @@ func TestResourcesPrioritizationHandler_PrioritizeResources(t *testing.T) { }{ { name: "non-empty report", + allPoliciesControls: map[string]reporthandling.Control{ + "C-001": ControlMock("C-001", 3, []string{"security"}, []string{"D"}), + "C-002": ControlMock("C-002", 4, []string{"security"}, []string{"B", "C"}), + "C-003": ControlMock("C-003", 10, []string{"security", "compliance"}, []string{"E"}), + }, results: map[string]resourcesresults.Result{ "resource1": { AssociatedControls: []resourcesresults.ResourceAssociatedControl{ @@ -95,9 +164,9 @@ func TestResourcesPrioritizationHandler_PrioritizeResources(t *testing.T) { "resource3": DeploymentWorkloadMock(1), }, expectedScores: map[string]float64{ - "resource1": float64(11), - "resource2": float64(7.199999999999999), - "resource3": float64(10.1), + "resource1": float64(84), + "resource2": float64(30.8), + "resource3": float64(11), }, expectedSeverity: map[string]int{ "resource1": apis.SeverityMedium, @@ -105,25 +174,23 @@ func TestResourcesPrioritizationHandler_PrioritizeResources(t *testing.T) { "resource3": apis.SeverityCritical, }, expectedControlsInVector: map[string][]string{ - "resource1": {"C-001", "C-002"}, - "resource2": {"C-001", "C-002"}, + "resource1": {"C-002", "C-002", "C-002", "C-001"}, + "resource2": {"C-002", "C-002", "C-002", "C-001"}, "resource3": {"C-003"}, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - handler := &ResourcesPrioritizationHandler{ - skipZeroScores: false, - } - sessionObj := OPASessionObjMock(tt.results, tt.controls, tt.resources) + handler, _ := NewResourcesPrioritizationHandler(&AttackTracksGetterMock{}) + sessionObj := OPASessionObjMock(tt.allPoliciesControls, tt.results, tt.controls, tt.resources) err := handler.PrioritizeResources(sessionObj) assert.NoError(t, err, "expected to have no errors in PrioritizeResources()") assert.Equalf(t, len(tt.results), len(sessionObj.ResourcesPrioritized), "expected prioritized resources to be not empty") for rId, resource := range sessionObj.ResourcesPrioritized { expectedScore := tt.expectedScores[rId] - assert.Equalf(t, expectedScore, resource.GetScore(), "expected score of resourceID '%s' to be '%v', got '%v'", rId, expectedScore, resource.GetScore()) + assert.InDeltaf(t, expectedScore, resource.GetScore(), 0.01, "expected score of resourceID '%s' to be '%v', got '%v'", rId, expectedScore, resource.GetScore()) expectedSeverity := tt.expectedSeverity[rId] assert.Equalf(t, expectedSeverity, resource.GetSeverity(), "expected severity of resourceID '%s' to be '%v', got '%v'", rId, expectedSeverity, resource.GetSeverity()) diff --git a/core/pkg/resultshandling/printer/v2/htmlprinter.go b/core/pkg/resultshandling/printer/v2/htmlprinter.go index 5e0dfe13..958c6e25 100644 --- a/core/pkg/resultshandling/printer/v2/htmlprinter.go +++ b/core/pkg/resultshandling/printer/v2/htmlprinter.go @@ -131,7 +131,7 @@ func buildResourceControlResult(resourceControl resourcesresults.ResourceAssocia ctlSeverity := apis.ControlSeverityToString(control.GetScoreFactor()) ctlName := resourceControl.GetName() ctlURL := resourceControl.GetID() - failedPaths := failedPathsToString(&resourceControl) + failedPaths := append(failedPathsToString(&resourceControl), fixPathsToString(&resourceControl)...) return ResourceControlResult{ctlSeverity, ctlName, ctlURL, failedPaths} } diff --git a/core/pkg/resultshandling/printer/v2/junit.go b/core/pkg/resultshandling/printer/v2/junit.go index a7a53d57..a32cbb21 100644 --- a/core/pkg/resultshandling/printer/v2/junit.go +++ b/core/pkg/resultshandling/printer/v2/junit.go @@ -4,7 +4,6 @@ import ( "encoding/xml" "fmt" "os" - "sort" "strings" logger "github.com/kubescape/go-logger" @@ -12,8 +11,9 @@ import ( "github.com/kubescape/k8s-interface/workloadinterface" "github.com/kubescape/kubescape/v2/core/cautils" "github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer" + "github.com/kubescape/opa-utils/reporthandling/apis" "github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary" - "github.com/kubescape/opa-utils/shared" + "github.com/kubescape/opa-utils/reporthandling/results/v1/resourcesresults" ) /* @@ -55,6 +55,7 @@ type JUnitTestSuite struct { Skipped string `xml:"skipped,attr"` // The total number of skipped tests Time string `xml:"time,attr"` // Time taken (in seconds) to execute the tests in the suite Timestamp string `xml:"timestamp,attr"` // when the test was executed in ISO 8601 format (2014-01-21T16:17:18) + File string `xml:"file,attr"` // The file be tested Properties []JUnitProperty `xml:"properties>property,omitempty"` TestCases []JUnitTestCase `xml:"testcase"` } @@ -88,6 +89,11 @@ type JUnitFailure struct { Contents string `xml:",chardata"` } +const ( + lineSeparator = "\n===================================================================================================================\n\n" + testCaseTypeResources = "Resources" +) + func NewJunitPrinter(verbose bool) *JunitPrinter { return &JunitPrinter{ verbose: verbose, @@ -118,96 +124,118 @@ func (junitPrinter *JunitPrinter) ActionPrint(opaSessionObj *cautils.OPASessionO func testsSuites(results *cautils.OPASessionObj) *JUnitTestSuites { return &JUnitTestSuites{ Suites: listTestsSuite(results), - Tests: results.Report.SummaryDetails.NumberOfControls().All(), + Tests: results.Report.SummaryDetails.NumberOfResources().All(), Name: "Kubescape Scanning", - Failures: results.Report.SummaryDetails.NumberOfControls().Failed(), + Failures: results.Report.SummaryDetails.NumberOfResources().Failed(), } } + +// aggregate resources source to a list of resources results +func sourceToResourcesResults(results *cautils.OPASessionObj) map[string][]resourcesresults.Result { + resourceResults := make(map[string][]resourcesresults.Result) + for i := range results.ResourceSource { + if r, ok := results.ResourcesResult[i]; ok { + if _, ok := resourceResults[results.ResourceSource[i].RelativePath]; !ok { + resourceResults[results.ResourceSource[i].RelativePath] = []resourcesresults.Result{} + } + resourceResults[results.ResourceSource[i].RelativePath] = append(resourceResults[results.ResourceSource[i].RelativePath], r) + } + } + return resourceResults +} + +// listTestsSuite returns a list of testsuites func listTestsSuite(results *cautils.OPASessionObj) []JUnitTestSuite { var testSuites []JUnitTestSuite - + resourceResults := sourceToResourcesResults(results) + counter := 0 // control scan - if len(results.Report.SummaryDetails.ListFrameworks()) == 0 { + for path, resourcesResult := range resourceResults { testSuite := JUnitTestSuite{} - testSuite.Failures = results.Report.SummaryDetails.NumberOfControls().Failed() testSuite.Timestamp = results.Report.ReportGenerationTime.String() - testSuite.ID = 0 - testSuite.Name = "kubescape" - testSuite.Properties = properties(results.Report.SummaryDetails.Score) - testSuite.TestCases = testsCases(results, &results.Report.SummaryDetails.Controls, "Kubescape") - testSuites = append(testSuites, testSuite) - return testSuites - } - - for i, f := range results.Report.SummaryDetails.Frameworks { - testSuite := JUnitTestSuite{} - testSuite.Failures = f.NumberOfControls().Failed() - testSuite.Timestamp = results.Report.ReportGenerationTime.String() - testSuite.ID = i - testSuite.Name = f.Name - testSuite.Properties = properties(f.Score) - testSuite.TestCases = testsCases(results, f.GetControls(), f.GetName()) - testSuites = append(testSuites, testSuite) + testSuite.ID = counter + counter++ + testSuite.File = path + testSuite.TestCases = testsCases(results, resourcesResult) + if len(testSuite.TestCases) > 0 { + testSuites = append(testSuites, testSuite) + } } return testSuites } -func testsCases(results *cautils.OPASessionObj, controls reportsummary.IControlsSummaries, classname string) []JUnitTestCase { - var testCases []JUnitTestCase - iter := controls.ListControlsIDs().All() - for iter.HasNext() { - cID := iter.Next() - testCase := JUnitTestCase{} - control := results.Report.SummaryDetails.Controls.GetControl(reportsummary.EControlCriteriaID, cID) - - testCase.Name = control.GetName() - testCase.Classname = classname - testCase.Status = string(control.GetStatus().Status()) - - if control.GetStatus().IsFailed() { - resources := map[string]interface{}{} - resourceIDs := control.ListResourcesIDs().Failed() - for j := range resourceIDs { - resource := results.AllResources[resourceIDs[j]] - resources[resourceToString(resource)] = nil +func failedControlsToFailureMessage(results *cautils.OPASessionObj, controls []resourcesresults.ResourceAssociatedControl, severityCounter []int) string { + msg := "" + for _, c := range controls { + control := results.Report.SummaryDetails.Controls.GetControl(reportsummary.EControlCriteriaID, c.GetID()) + if c.GetStatus(nil).IsFailed() { + msg += fmt.Sprintf("Test: %s\n", control.GetName()) + msg += fmt.Sprintf("Severity: %s\n", apis.ControlSeverityToString(control.GetScoreFactor())) + msg += fmt.Sprintf("Remediation: %s\n", control.GetRemediation()) + msg += fmt.Sprintf("Link: %s\n", getControlLink(control.GetID())) + if failedPaths := failedPathsToString(&c); len(failedPaths) > 0 { + msg += fmt.Sprintf("Failed paths: \n - %s\n", strings.Join(failedPaths, "\n - ")) } - resourcesStr := shared.MapStringToSlice(resources) - sort.Strings(resourcesStr) - testCaseFailure := JUnitFailure{} - testCaseFailure.Type = "Control" - // testCaseFailure.Contents = - testCaseFailure.Message = fmt.Sprintf("Remediation: %s\nMore details: %s\n\n%s", control.GetRemediation(), getControlLink(control.GetID()), strings.Join(resourcesStr, "\n")) - - testCase.Failure = &testCaseFailure - } else if control.GetStatus().IsSkipped() { - testCase.SkipMessage = &JUnitSkipMessage{ - Message: "", // TODO - fill after statusInfo is supported + if fixPaths := fixPathsToString(&c); len(fixPaths) > 0 { + msg += fmt.Sprintf("Available fix: \n - %s\n", strings.Join(fixPaths, "\n - ")) } + msg += "\n" + severityCounter[apis.ControlSeverityToInt(control.GetScoreFactor())] += 1 } + } + return msg +} + +// Every testCase includes a file (even if the file contains several resources) +func testsCases(results *cautils.OPASessionObj, resourcesResult []resourcesresults.Result) []JUnitTestCase { + var testCases []JUnitTestCase + testCase := JUnitTestCase{} + testCaseFailure := JUnitFailure{} + testCaseFailure.Type = testCaseTypeResources + message := "" + + // severityCounter represents the severities, 0: Unknown, 1: Low, 2: Medium, 3: High, 4: Critical + severityCounter := make([]int, apis.NumberOfSeverities, apis.NumberOfSeverities) + + for i := range resourcesResult { + if failedControls := failedControlsToFailureMessage(results, resourcesResult[i].ListControls(), severityCounter); failedControls != "" { + message += fmt.Sprintf("%sResource: %s\n\n%s", lineSeparator, resourceNameToString(results.AllResources[resourcesResult[i].GetResourceID()]), failedControls) + } + } + testCaseFailure.Message += fmt.Sprintf("%s\n%s", getSummaryMessage(severityCounter), message) + + testCase.Failure = &testCaseFailure + if testCase.Failure.Message != "" { testCases = append(testCases, testCase) } + return testCases } -func resourceToString(resource workloadinterface.IMetadata) string { - sep := "; " - s := "" - s += fmt.Sprintf("apiVersion: %s", resource.GetApiVersion()) + sep - s += fmt.Sprintf("kind: %s", resource.GetKind()) + sep - if resource.GetNamespace() != "" { - s += fmt.Sprintf("namespace: %s", resource.GetNamespace()) + sep +func getSummaryMessage(severityCounter []int) string { + total := 0 + severities := "" + for i, count := range severityCounter { + if apis.SeverityNumberToString(i) == apis.SeverityNumberToString(apis.SeverityUnknown) { + continue + } + severities += fmt.Sprintf("%s: %d, ", apis.SeverityNumberToString(i), count) + total += count } - s += fmt.Sprintf("name: %s", resource.GetName()) - return s + if len(severities) == 0 { + return "" + } + return fmt.Sprintf("Total: %d (%s)", total, severities[:len(severities)-2]) } -func properties(riskScore float32) []JUnitProperty { - return []JUnitProperty{ - { - Name: "riskScore", - Value: fmt.Sprintf("%.2f", riskScore), - }, +func resourceNameToString(resource workloadinterface.IMetadata) string { + s := "" + s += fmt.Sprintf("kind=%s/", resource.GetKind()) + if resource.GetNamespace() != "" { + s += fmt.Sprintf("namespace=%s/", resource.GetNamespace()) } + s += fmt.Sprintf("name=%s", resource.GetName()) + return s } diff --git a/core/pkg/resultshandling/printer/v2/prettyprinter.go b/core/pkg/resultshandling/printer/v2/prettyprinter.go index 5cee44fc..87cef538 100644 --- a/core/pkg/resultshandling/printer/v2/prettyprinter.go +++ b/core/pkg/resultshandling/printer/v2/prettyprinter.go @@ -192,7 +192,10 @@ func (prettyPrinter *PrettyPrinter) printSummaryTable(summaryDetails *reportsumm fmt.Fprintf(prettyPrinter.writer, "\nKubescape did not scan any of the resources, make sure you are scanning valid kubernetes manifests (Deployments, Pods, etc.)\n") return } - cautils.InfoTextDisplay(prettyPrinter.writer, "\n"+controlCountersForSummary(summaryDetails.NumberOfControls())+"\n\n") + cautils.InfoTextDisplay(prettyPrinter.writer, "\n"+controlCountersForSummary(summaryDetails.NumberOfControls())+"\n") + cautils.InfoTextDisplay(prettyPrinter.writer, renderSeverityCountersSummary(&summaryDetails.SeverityCounters)+"\n\n") + + // cautils.InfoTextDisplay(prettyPrinter.writer, "\n"+"Severities: SOME OTHER"+"\n\n") summaryTable := tablewriter.NewWriter(prettyPrinter.writer) summaryTable.SetAutoWrapText(false) @@ -256,6 +259,19 @@ func getControlLink(controlID string) string { return fmt.Sprintf("https://hub.armosec.io/docs/%s", strings.ToLower(controlID)) } +// renderSeverityCountersSummary renders the string that reports severity counters summary +func renderSeverityCountersSummary(counters reportsummary.ISeverityCounters) string { + critical := counters.NumberOfResourcesWithCriticalSeverity() + high := counters.NumberOfResourcesWithHighSeverity() + medium := counters.NumberOfResourcesWithMediumSeverity() + low := counters.NumberOfResourcesWithLowSeverity() + + return fmt.Sprintf( + "Failed Resources by Severity: Critical — %d, High — %d, Medium — %d, Low — %d", + critical, high, medium, low, + ) +} + func controlCountersForSummary(counters reportsummary.ICounters) string { return fmt.Sprintf("Controls: %d (Failed: %d, Excluded: %d, Skipped: %d)", counters.All(), counters.Failed(), counters.Excluded(), counters.Skipped()) } diff --git a/core/pkg/resultshandling/printer/v2/resourcetable.go b/core/pkg/resultshandling/printer/v2/resourcetable.go index 28951ec0..27ffa3d1 100644 --- a/core/pkg/resultshandling/printer/v2/resourcetable.go +++ b/core/pkg/resultshandling/printer/v2/resourcetable.go @@ -74,7 +74,7 @@ func generateResourceRows(controls []resourcesresults.ResourceAssociatedControl, } row[resourceColumnURL] = fmt.Sprintf("https://hub.armosec.io/docs/%s", strings.ToLower(controls[i].GetID())) - row[resourceColumnPath] = strings.Join(failedPathsToString(&controls[i]), "\n") + row[resourceColumnPath] = strings.Join(append(failedPathsToString(&controls[i]), fixPathsToString(&controls[i])...), "\n") row[resourceColumnName] = controls[i].GetName() if c := summaryDetails.Controls.GetControl(reportsummary.EControlCriteriaName, controls[i].GetName()); c != nil { @@ -120,6 +120,16 @@ func failedPathsToString(control *resourcesresults.ResourceAssociatedControl) [] if p := control.ResourceAssociatedRules[j].Paths[k].FailedPath; p != "" { paths = append(paths, p) } + } + } + return paths +} + +func fixPathsToString(control *resourcesresults.ResourceAssociatedControl) []string { + var paths []string + + for j := range control.ResourceAssociatedRules { + for k := range control.ResourceAssociatedRules[j].Paths { if p := control.ResourceAssociatedRules[j].Paths[k].FixPath.Path; p != "" { v := control.ResourceAssociatedRules[j].Paths[k].FixPath.Value paths = append(paths, fmt.Sprintf("%s=%s", p, v)) diff --git a/core/pkg/resultshandling/reporter/v2/reporteventreceiver.go b/core/pkg/resultshandling/reporter/v2/reporteventreceiver.go index 809825ee..1de82e9d 100644 --- a/core/pkg/resultshandling/reporter/v2/reporteventreceiver.go +++ b/core/pkg/resultshandling/reporter/v2/reporteventreceiver.go @@ -139,24 +139,21 @@ func (report *ReportEventReceiver) sendResources(host string, opaSessionObj *cau func (report *ReportEventReceiver) setResults(reportObj *reporthandlingv2.PostureReport, results map[string]resourcesresults.Result, allResources map[string]workloadinterface.IMetadata, resourcesSource map[string]reporthandling.Source, prioritizedResources map[string]prioritization.PrioritizedResource, counter, reportCounter *int, host string) error { for _, v := range results { - /* + // set result.RawResource + resourceID := v.GetResourceID() + if _, ok := allResources[resourceID]; !ok { + return fmt.Errorf("expected to find raw resource object for '%s'", resourceID) + } + resource := reporthandling.NewResourceIMetadata(allResources[resourceID]) + if r, ok := resourcesSource[resourceID]; ok { + resource.SetSource(&r) + } + v.RawResource = resource - // set result.RawResource - resourceID := v.GetResourceID() - if _, ok := allResources[resourceID]; !ok { - return fmt.Errorf("expected to find raw resource object for '%s'", resourceID) - } - resource := reporthandling.NewResourceIMetadata(allResources[resourceID]) - if r, ok := resourcesSource[resourceID]; ok { - resource.SetSource(&r) - } - v.RawResource = resource - - // set result.PrioritizedResource - if resource, ok := prioritizedResources[resourceID]; ok { - v.PrioritizedResource = &resource - } - */ + // set result.PrioritizedResource + if results, ok := prioritizedResources[resourceID]; ok { + v.PrioritizedResource = &results + } r, err := json.Marshal(v) if err != nil { diff --git a/go.mod b/go.mod index af064f47..d0919c52 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/johnfercher/maroto v0.37.0 github.com/kubescape/go-logger v0.0.6 github.com/kubescape/k8s-interface v0.0.83 - github.com/kubescape/opa-utils v0.0.183 + github.com/kubescape/opa-utils v0.0.192 github.com/kubescape/rbac-utils v0.0.17 github.com/libgit2/git2go/v33 v33.0.9 github.com/mattn/go-isatty v0.0.14 @@ -115,6 +115,7 @@ require ( github.com/mattn/go-runewidth v0.0.9 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect diff --git a/go.sum b/go.sum index 4308189c..2601e141 100644 --- a/go.sum +++ b/go.sum @@ -839,8 +839,8 @@ github.com/kubescape/go-logger v0.0.6 h1:ynhAmwrz0O7Jtqq1CdmCZUrKveji25hVP+B/FAb github.com/kubescape/go-logger v0.0.6/go.mod h1:DnVWEvC90LFY1nNMaNo6nBVOcqkLMK3S0qzXP1fzRvI= github.com/kubescape/k8s-interface v0.0.83 h1:yQ1kWNZmKfBim/+NmxpPI/j7L9ASDq2h3mCNdmYgzqY= github.com/kubescape/k8s-interface v0.0.83/go.mod h1:ihX96yqar+xogHl45mFE8zT9DLI06iy7XQPAP+j5KJE= -github.com/kubescape/opa-utils v0.0.183 h1:Qiosb2dV6QIIM8vIbjiIRkxYNiHFbDTvcOVfKa8u5mE= -github.com/kubescape/opa-utils v0.0.183/go.mod h1:jC5QrhS6WiFj/tXP2/YSDBnFlGsYUQokDGbKwMBgMpw= +github.com/kubescape/opa-utils v0.0.192 h1:eV3+v3dPWA8F5nZdVIxB9GbCqjQj0AzmLCCW/oGWz2M= +github.com/kubescape/opa-utils v0.0.192/go.mod h1:frMpD9wuK6rE3tMRIy6EM1X9zi+sSbXoDWjcSaq11og= github.com/kubescape/rbac-utils v0.0.17 h1:B78kjlTKqjYK/PXwmi4GPysHsFxIwVz1KFb4+IGT29w= github.com/kubescape/rbac-utils v0.0.17/go.mod h1:pBwjpcrVeuH/no+DiCZWvlhYtCDzd3U0o/hEZKi+eM8= github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo= @@ -899,6 +899,8 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= diff --git a/httphandler/go.mod b/httphandler/go.mod index c283493f..e3d794c3 100644 --- a/httphandler/go.mod +++ b/httphandler/go.mod @@ -12,7 +12,7 @@ require ( github.com/gorilla/schema v1.2.0 github.com/kubescape/go-logger v0.0.6 github.com/kubescape/kubescape/v2 v2.0.0-00010101000000-000000000000 - github.com/kubescape/opa-utils v0.0.183 + github.com/kubescape/opa-utils v0.0.192 github.com/stretchr/testify v1.8.0 k8s.io/utils v0.0.0-20220706174534-f6158b442e7c ) @@ -117,7 +117,7 @@ require ( github.com/mattn/go-runewidth v0.0.9 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/mitchellh/mapstructure v1.4.3 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect diff --git a/httphandler/go.sum b/httphandler/go.sum index 49c20842..29fd14ac 100644 --- a/httphandler/go.sum +++ b/httphandler/go.sum @@ -895,8 +895,8 @@ github.com/kubescape/go-logger v0.0.6 h1:ynhAmwrz0O7Jtqq1CdmCZUrKveji25hVP+B/FAb github.com/kubescape/go-logger v0.0.6/go.mod h1:DnVWEvC90LFY1nNMaNo6nBVOcqkLMK3S0qzXP1fzRvI= github.com/kubescape/k8s-interface v0.0.83 h1:yQ1kWNZmKfBim/+NmxpPI/j7L9ASDq2h3mCNdmYgzqY= github.com/kubescape/k8s-interface v0.0.83/go.mod h1:ihX96yqar+xogHl45mFE8zT9DLI06iy7XQPAP+j5KJE= -github.com/kubescape/opa-utils v0.0.183 h1:Qiosb2dV6QIIM8vIbjiIRkxYNiHFbDTvcOVfKa8u5mE= -github.com/kubescape/opa-utils v0.0.183/go.mod h1:jC5QrhS6WiFj/tXP2/YSDBnFlGsYUQokDGbKwMBgMpw= +github.com/kubescape/opa-utils v0.0.192 h1:eV3+v3dPWA8F5nZdVIxB9GbCqjQj0AzmLCCW/oGWz2M= +github.com/kubescape/opa-utils v0.0.192/go.mod h1:frMpD9wuK6rE3tMRIy6EM1X9zi+sSbXoDWjcSaq11og= github.com/kubescape/rbac-utils v0.0.17 h1:B78kjlTKqjYK/PXwmi4GPysHsFxIwVz1KFb4+IGT29w= github.com/kubescape/rbac-utils v0.0.17/go.mod h1:pBwjpcrVeuH/no+DiCZWvlhYtCDzd3U0o/hEZKi+eM8= github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo= @@ -959,8 +959,9 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= From 291668647c2bf794c6b99227512cf48eba943dc5 Mon Sep 17 00:00:00 2001 From: Suhas Gumma <43647369+suhasgumma@users.noreply.github.com> Date: Thu, 22 Sep 2022 18:29:08 +0530 Subject: [PATCH 2/2] build killercoda playground added (#843) --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index 83b358f1..e75421c9 100644 --- a/README.md +++ b/README.md @@ -365,6 +365,28 @@ View Kubescape scan results directly in [Lens IDE](https://k8slens.dev/) using k +## Build on pre-configured killercoda's ubuntu playground + +* [Pre-configured Killercoda's Ubuntu Playground](https://killercoda.com/suhas-gumma/scenario/kubescape-build-for-development) + +
Pre-programmed actions executed by the playground + + +* Clone the official GitHub repository of `Kubescape`. +* [Automate the build process on Linux](https://github.com/kubescape/kubescape#build-on-linuxmacos) +* The entire process involves executing multiple commands in order and it takes around 5-6 minutes to execute them all. + +
+ +
+Instructions to use the playground + +* Apply changes you wish to make to the kubescape directory using text editors like `Vim`. +* [Build on Linux](https://github.com/kubescape/kubescape#build-on-linuxmacos) +* Now, you can use Kubescape just like a normal user. Instead of using `kubescape`, use `./kubescape`. (Make sure you are inside kubescape directory because the command will execute the binary named `kubescape` in `kubescape directory`. + +
+ ## VS code configuration samples You can use the sample files below to setup your VS code environment for building and debugging purposes.