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 <vladklokun@users.noreply.github.com>
This commit is contained in:
David Wertenteil
2022-09-18 10:34:34 +03:00
committed by GitHub
parent f4bb03039a
commit 196d07edc6
10 changed files with 99 additions and 50 deletions

View File

@@ -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
},
}

View File

@@ -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")

View File

@@ -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")

View File

@@ -100,33 +100,36 @@ 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 differnet 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
CustomClusterName string // Custom name of the cluster
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
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 {

View File

@@ -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 {

View File

@@ -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())
}

2
go.mod
View File

@@ -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.188
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

4
go.sum
View File

@@ -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.188 h1:Fjj16X1Nfurvn1Aqe4ybtSPup5UDvHJW2oAS/JIqBsQ=
github.com/kubescape/opa-utils v0.0.188/go.mod h1:frMpD9wuK6rE3tMRIy6EM1X9zi+sSbXoDWjcSaq11og=
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=

View File

@@ -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.188
github.com/kubescape/opa-utils v0.0.192
github.com/stretchr/testify v1.8.0
k8s.io/utils v0.0.0-20220706174534-f6158b442e7c
)

View File

@@ -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.188 h1:Fjj16X1Nfurvn1Aqe4ybtSPup5UDvHJW2oAS/JIqBsQ=
github.com/kubescape/opa-utils v0.0.188/go.mod h1:frMpD9wuK6rE3tMRIy6EM1X9zi+sSbXoDWjcSaq11og=
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=