mirror of
https://github.com/kubescape/kubescape.git
synced 2026-02-14 18:09:55 +00:00
merge
This commit is contained in:
@@ -72,6 +72,8 @@ 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. e.g: --exclude-namespaces ns-a,ns-b. Notice, when running with `exclude-namespace` kubescape does not scan cluster-scoped objects.")
|
||||
scanCmd.PersistentFlags().StringVarP(&scanInfo.ChartPath, "chart", "c", "", "Path to a Helm chart. If not set will download Helm chart from ARMO management portal")
|
||||
scanCmd.PersistentFlags().StringVarP(&scanInfo.FilePath, "file", "i", "", "Path to a YAML file or a directory containing YAML files. If not set will download YAML files from ARMO management portal")
|
||||
|
||||
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().Float32VarP(&scanInfo.ComplianceThreshold, "compliance-threshold", "", 0, "Compliance threshold is the percent below which the command fails and returns exit code 1")
|
||||
@@ -91,6 +93,7 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
|
||||
scanCmd.PersistentFlags().BoolVarP(&scanInfo.Submit, "submit", "", false, "Submit the scan results to Kubescape SaaS 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().BoolVarP(&scanInfo.OmitRawResources, "omit-raw-resources", "", false, "Omit raw resources from the output. By default the raw resources are included in the output")
|
||||
scanCmd.PersistentFlags().BoolVarP(&scanInfo.PrintAttackTree, "print-attack-tree", "", false, "Print attack tree")
|
||||
scanCmd.PersistentFlags().BoolVarP(&scanInfo.ScanImages, "scan-images", "", false, "Scan images")
|
||||
|
||||
scanCmd.PersistentFlags().MarkDeprecated("silent", "use '--logger' flag instead. Flag will be removed at 1.May.2022")
|
||||
scanCmd.PersistentFlags().MarkDeprecated("fail-threshold", "use '--compliance-threshold' flag instead. Flag will be removed at 1.Dec.2023")
|
||||
|
||||
@@ -3,6 +3,7 @@ package cautils
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/anchore/grype/grype/presenter/models"
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/kubescape/k8s-interface/workloadinterface"
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
@@ -18,6 +19,11 @@ import (
|
||||
type K8SResources map[string][]string
|
||||
type KSResources map[string][]string
|
||||
|
||||
type ImageScanData struct {
|
||||
PresenterConfig *models.PresenterConfig
|
||||
Image string
|
||||
}
|
||||
|
||||
type ScanTypes string
|
||||
|
||||
const (
|
||||
@@ -48,6 +54,7 @@ type OPASessionObj struct {
|
||||
Exceptions []armotypes.PostureExceptionPolicy // list of exceptions to apply on scan results
|
||||
OmitRawResources bool // omit raw resources from output
|
||||
ScanType ScanTypes // scan type
|
||||
ResourceIDToImageMap map[string][]string
|
||||
}
|
||||
|
||||
func NewOPASessionObj(ctx context.Context, frameworks []reporthandling.Framework, k8sResources K8SResources, scanInfo *ScanInfo) *OPASessionObj {
|
||||
|
||||
@@ -8,7 +8,9 @@ import (
|
||||
)
|
||||
|
||||
// NativeFrameworks identifies all pre-built, native frameworks.
|
||||
var NativeFrameworks = []string{"security", "mitre", "nsa"}
|
||||
var NativeFrameworks = []string{"clusterscan", "mitre", "nsa"}
|
||||
|
||||
// var NativeFrameworks = []string{"clusterscan"}
|
||||
|
||||
type (
|
||||
// TenantResponse holds the credentials for a tenant.
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"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"
|
||||
"github.com/kubescape/kubescape/v2/pkg/imagescan"
|
||||
apisv1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
|
||||
"go.opentelemetry.io/otel"
|
||||
|
||||
@@ -206,6 +207,9 @@ func (ks *Kubescape) Scan(ctx context.Context, scanInfo *cautils.ScanInfo) (*res
|
||||
spanPrioritization.End()
|
||||
}
|
||||
|
||||
if scanInfo.ScanImages && len(scanData.ResourceIDToImageMap) > 0 {
|
||||
scanImages(scanInfo, scanData, ctx, resultsHandling)
|
||||
}
|
||||
// ========================= results handling =====================
|
||||
resultsHandling.SetData(scanData)
|
||||
|
||||
@@ -216,6 +220,35 @@ func (ks *Kubescape) Scan(ctx context.Context, scanInfo *cautils.ScanInfo) (*res
|
||||
return resultsHandling, nil
|
||||
}
|
||||
|
||||
func scanImages(scanInfo *cautils.ScanInfo, scanData *cautils.OPASessionObj, ctx context.Context, resultsHandling *resultshandling.ResultsHandler) {
|
||||
logger.L().Ctx(ctx).Info("Scanning images")
|
||||
|
||||
dbCfg, _ := imagescan.NewDefaultDBConfig()
|
||||
svc := imagescan.NewScanService(dbCfg)
|
||||
|
||||
for _, imgs := range scanData.ResourceIDToImageMap {
|
||||
for _, img := range imgs {
|
||||
scanSingleImage(ctx, img, svc, resultsHandling)
|
||||
}
|
||||
}
|
||||
logger.L().Ctx(ctx).Success("Finished scanning images")
|
||||
}
|
||||
|
||||
func scanSingleImage(ctx context.Context, img string, svc imagescan.Service, resultsHandling *resultshandling.ResultsHandler) {
|
||||
logger.L().Ctx(ctx).Debug(fmt.Sprintf("Scanning image: %s", img))
|
||||
|
||||
scanResults, err := svc.Scan(ctx, img)
|
||||
if err != nil {
|
||||
logger.L().Ctx(ctx).Error(fmt.Sprintf("failed to scan image: %s", img), helpers.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
resultsHandling.ImageScanData = append(resultsHandling.ImageScanData, cautils.ImageScanData{
|
||||
Image: img,
|
||||
PresenterConfig: scanResults,
|
||||
})
|
||||
}
|
||||
|
||||
func isPrioritizationScanType(scanType cautils.ScanTypes) bool {
|
||||
return scanType == cautils.ScanTypeCluster || scanType == cautils.ScanTypeRepo
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ func ConvertFrameworksToSummaryDetails(summaryDetails *reportsummary.SummaryDeta
|
||||
ScoreFactor: frameworks[i].Controls[j].BaseScore,
|
||||
Description: frameworks[i].Controls[j].Description,
|
||||
Remediation: frameworks[i].Controls[j].Remediation,
|
||||
Categories: frameworks[i].Controls[j].Categories,
|
||||
Category: frameworks[i].Controls[j].Category,
|
||||
}
|
||||
if frameworks[i].Controls[j].GetActionRequiredAttribute() == string(apis.SubStatusManualReview) {
|
||||
c.Status = apis.StatusSkipped
|
||||
|
||||
@@ -32,9 +32,15 @@ func NewFileResourceHandler(_ context.Context, inputPatterns []string, workloadI
|
||||
}
|
||||
}
|
||||
|
||||
func (fileHandler *FileResourceHandler) GetResources(ctx context.Context, sessionObj *cautils.OPASessionObj, _ *identifiers.PortalDesignator, progressListener opaprocessor.IJobProgressNotificationClient) (cautils.K8SResources, map[string]workloadinterface.IMetadata, cautils.KSResources, map[string]bool, error) {
|
||||
func (fileHandler *FileResourceHandler) GetResources(ctx context.Context, sessionObj *cautils.OPASessionObj, _ *identifiers.PortalDesignator, progressListener opaprocessor.IJobProgressNotificationClient, isImageScan bool) (*cautils.K8SResources, map[string]workloadinterface.IMetadata, *cautils.KSResources, map[string][]string, error) {
|
||||
|
||||
//
|
||||
// build resources map
|
||||
// map resources based on framework required resources: map["/group/version/kind"][]<k8s workloads ids>
|
||||
k8sResources := setK8sResourceMap(sessionObj.Policies)
|
||||
allResources := map[string]workloadinterface.IMetadata{}
|
||||
ksResources := cautils.KSResources{}
|
||||
ksResources := &cautils.KSResources{}
|
||||
resourceIDToImages := make(map[string][]string, 0)
|
||||
|
||||
if len(fileHandler.inputPatterns) == 0 {
|
||||
return nil, nil, nil, nil, fmt.Errorf("missing input")
|
||||
@@ -53,6 +59,16 @@ func (fileHandler *FileResourceHandler) GetResources(ctx context.Context, sessio
|
||||
logger.L().Debug("path ignored because contains only a non-kubernetes file", helpers.String("path", fileHandler.inputPatterns[path]))
|
||||
}
|
||||
|
||||
if isImageScan {
|
||||
for _, workload := range workloads {
|
||||
wlObj := workloadinterface.NewWorkloadObj(workload.GetObject())
|
||||
containers, _ := wlObj.GetContainers()
|
||||
for _, container := range containers {
|
||||
resourceIDToImages[workload.GetID()] = append(resourceIDToImages[workload.GetID()], container.Image)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range workloadIDToSource {
|
||||
sessionObj.ResourceSource[k] = v
|
||||
}
|
||||
@@ -124,6 +140,7 @@ func (fileHandler *FileResourceHandler) findWorkloadToScan(mappedResources map[s
|
||||
}
|
||||
|
||||
return wls[0], nil
|
||||
// return k8sResources, allResources, ksResources, resourceIDToImages, nil
|
||||
}
|
||||
|
||||
func getResourcesFromPath(ctx context.Context, path string) (map[string]reporthandling.Source, []workloadinterface.IMetadata, error) {
|
||||
@@ -238,6 +255,7 @@ func getResourcesFromPath(ctx context.Context, path string) (map[string]reportha
|
||||
}
|
||||
|
||||
workloadSource := reporthandling.Source{
|
||||
Path: path,
|
||||
RelativePath: source,
|
||||
FileType: reporthandling.SourceTypeHelmChart,
|
||||
HelmChartName: helmChartName,
|
||||
|
||||
@@ -19,8 +19,46 @@ import (
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
func CollectResources(ctx context.Context, rsrcHandler IResourceHandler, policyIdentifier []cautils.PolicyIdentifier, opaSessionObj *cautils.OPASessionObj, progressListener opaprocessor.IJobProgressNotificationClient) error {
|
||||
ctx, span := otel.Tracer("").Start(ctx, "resourcehandler.CollectResources")
|
||||
// PolicyHandler -
|
||||
type PolicyHandler struct {
|
||||
resourceHandler resourcehandler.IResourceHandler
|
||||
// we are listening on this chan in opaprocessor/processorhandler.go/ProcessRulesListener func
|
||||
getters *cautils.Getters
|
||||
}
|
||||
|
||||
// CreatePolicyHandler Create ws-handler obj
|
||||
func NewPolicyHandler(resourceHandler resourcehandler.IResourceHandler) *PolicyHandler {
|
||||
return &PolicyHandler{
|
||||
resourceHandler: resourceHandler,
|
||||
}
|
||||
}
|
||||
|
||||
func (policyHandler *PolicyHandler) CollectResources(ctx context.Context, policyIdentifier []cautils.PolicyIdentifier, scanInfo *cautils.ScanInfo, progressListener opaprocessor.IJobProgressNotificationClient) (*cautils.OPASessionObj, error) {
|
||||
opaSessionObj := cautils.NewOPASessionObj(ctx, nil, nil, scanInfo)
|
||||
|
||||
// validate notification
|
||||
// TODO
|
||||
policyHandler.getters = &scanInfo.Getters
|
||||
|
||||
// get policies
|
||||
if err := policyHandler.getPolicies(ctx, policyIdentifier, opaSessionObj); err != nil {
|
||||
return opaSessionObj, err
|
||||
}
|
||||
|
||||
err := policyHandler.getResources(ctx, policyIdentifier, opaSessionObj, progressListener, scanInfo.ScanImages)
|
||||
if err != nil {
|
||||
return opaSessionObj, err
|
||||
}
|
||||
if (opaSessionObj.K8SResources == nil || len(*opaSessionObj.K8SResources) == 0) && (opaSessionObj.ArmoResource == nil || len(*opaSessionObj.ArmoResource) == 0) {
|
||||
return opaSessionObj, fmt.Errorf("empty list of resources")
|
||||
}
|
||||
|
||||
// update channel
|
||||
return opaSessionObj, nil
|
||||
}
|
||||
|
||||
func (policyHandler *PolicyHandler) getResources(ctx context.Context, policyIdentifier []cautils.PolicyIdentifier, opaSessionObj *cautils.OPASessionObj, progressListener opaprocessor.IJobProgressNotificationClient, isImageScan bool) error {
|
||||
ctx, span := otel.Tracer("").Start(ctx, "policyHandler.getResources")
|
||||
defer span.End()
|
||||
opaSessionObj.Report.ClusterAPIServerInfo = rsrcHandler.GetClusterAPIServerInfo(ctx)
|
||||
|
||||
@@ -30,6 +68,7 @@ func CollectResources(ctx context.Context, rsrcHandler IResourceHandler, policyI
|
||||
}
|
||||
|
||||
resourcesMap, allResources, ksResources, excludedRulesMap, err := rsrcHandler.GetResources(ctx, opaSessionObj, &policyIdentifier[0].Designators, progressListener)
|
||||
resourcesMap, allResources, ksResources, resourceIDToImages, err := policyHandler.resourceHandler.GetResources(ctx, opaSessionObj, &policyIdentifier[0].Designators, progressListener, isImageScan)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -38,6 +77,11 @@ func CollectResources(ctx context.Context, rsrcHandler IResourceHandler, policyI
|
||||
opaSessionObj.AllResources = allResources
|
||||
opaSessionObj.KubescapeResource = ksResources
|
||||
opaSessionObj.ExcludedRules = excludedRulesMap
|
||||
opaSessionObj.ArmoResource = ksResources
|
||||
opaSessionObj.ResourceIDToImageMap = resourceIDToImages
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if (opaSessionObj.K8SResources == nil || len(opaSessionObj.K8SResources) == 0) && (opaSessionObj.KubescapeResource == nil || len(opaSessionObj.KubescapeResource) == 0) {
|
||||
return fmt.Errorf("empty list of resources")
|
||||
|
||||
@@ -87,7 +87,29 @@ func (k8sHandler *K8sResourceHandler) GetResources(ctx context.Context, sessionO
|
||||
k8sResourcesMap, allResources, err := k8sHandler.pullResources(queryableResources, namespace, labels)
|
||||
if err != nil {
|
||||
cautils.StopSpinner()
|
||||
return k8sResourcesMap, allResources, ksResourceMap, excludedRulesMap, err
|
||||
return k8sResourcesMap, allResources, ksResourceMap, nil, err
|
||||
}
|
||||
|
||||
resourceIDToImages := make(map[string][]string, 0)
|
||||
if isImageScan {
|
||||
for _, workload := range allResources {
|
||||
wlObj := workloadinterface.NewWorkloadObj(workload.GetObject())
|
||||
containers, _ := wlObj.GetContainers()
|
||||
for _, container := range containers {
|
||||
resourceIDToImages[workload.GetID()] = append(resourceIDToImages[workload.GetID()], container.Image)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resourceIDToImages := make(map[string][]string, 0)
|
||||
if isImageScan {
|
||||
for _, workload := range allResources {
|
||||
wlObj := workloadinterface.NewWorkloadObj(workload.GetObject())
|
||||
containers, _ := wlObj.GetContainers()
|
||||
for _, container := range containers {
|
||||
resourceIDToImages[workload.GetID()] = append(resourceIDToImages[workload.GetID()], container.Image)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add workload to k8s resources map (for single workload scan)
|
||||
@@ -163,6 +185,7 @@ func (k8sHandler *K8sResourceHandler) GetResources(ctx context.Context, sessionO
|
||||
}
|
||||
|
||||
return k8sResourcesMap, allResources, ksResourceMap, excludedRulesMap, nil
|
||||
return k8sResourcesMap, allResources, ksResourceMap, resourceIDToImages, nil
|
||||
}
|
||||
|
||||
func (k8sHandler *K8sResourceHandler) findWorkloadToScan(workloadIdentifier *cautils.WorkloadIdentifier) (workloadinterface.IWorkload, error) {
|
||||
@@ -349,6 +372,8 @@ func (k8sHandler *K8sResourceHandler) pullResources(queryableResources Queryable
|
||||
}
|
||||
}
|
||||
return k8sResources, allResources, errs
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func (k8sHandler *K8sResourceHandler) pullSingleResource(resource *schema.GroupVersionResource, namespace string, labels map[string]string, fields string) ([]unstructured.Unstructured, error) {
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/anchore/grype/grype/presenter/models"
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
@@ -26,7 +25,7 @@ const (
|
||||
|
||||
type IPrinter interface {
|
||||
PrintNextSteps()
|
||||
ActionPrint(ctx context.Context, opaSessionObj *cautils.OPASessionObj, imageScanData *models.PresenterConfig)
|
||||
ActionPrint(ctx context.Context, opaSessionObj *cautils.OPASessionObj, imageScanData []cautils.ImageScanData)
|
||||
SetWriter(ctx context.Context, outputFile string)
|
||||
Score(score float32)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ const (
|
||||
jsonOutputExt = ".json"
|
||||
)
|
||||
|
||||
var _ printer.IPrinter = &JsonPrinter{}
|
||||
|
||||
type JsonPrinter struct {
|
||||
writer *os.File
|
||||
}
|
||||
@@ -48,7 +50,7 @@ func (jsonPrinter *JsonPrinter) PrintNextSteps() {
|
||||
|
||||
}
|
||||
|
||||
func (jsonPrinter *JsonPrinter) ActionPrint(ctx context.Context, opaSessionObj *cautils.OPASessionObj, imageScanData *models.PresenterConfig) {
|
||||
func (jsonPrinter *JsonPrinter) ActionPrint(ctx context.Context, opaSessionObj *cautils.OPASessionObj, _ []cautils.ImageScanData) {
|
||||
report := cautils.ReportV2ToV1(opaSessionObj)
|
||||
|
||||
var postureReportStr []byte
|
||||
|
||||
@@ -59,7 +59,7 @@ func (hp *HtmlPrinter) PrintNextSteps() {
|
||||
|
||||
}
|
||||
|
||||
func (hp *HtmlPrinter) ActionPrint(ctx context.Context, opaSessionObj *cautils.OPASessionObj, imageScanData *models.PresenterConfig) {
|
||||
func (hp *HtmlPrinter) ActionPrint(ctx context.Context, opaSessionObj *cautils.OPASessionObj, imageScanData []cautils.ImageScanData) {
|
||||
tplFuncMap := template.FuncMap{
|
||||
"sum": func(nums ...int) int {
|
||||
total := 0
|
||||
|
||||
@@ -72,14 +72,14 @@ func printImageAndConfigurationScanning(output io.Writer, imageScanData *models.
|
||||
return enc.Encode(&docForJson)
|
||||
}
|
||||
|
||||
func (jp *JsonPrinter) ActionPrint(ctx context.Context, opaSessionObj *cautils.OPASessionObj, imageScanData *models.PresenterConfig) {
|
||||
func (jp *JsonPrinter) ActionPrint(ctx context.Context, opaSessionObj *cautils.OPASessionObj, imageScanData []cautils.ImageScanData) {
|
||||
var err error
|
||||
if opaSessionObj != nil && imageScanData != nil {
|
||||
err = printImageAndConfigurationScanning(jp.writer, imageScanData, opaSessionObj)
|
||||
err = printImageAndConfigurationScanning(jp.writer, imageScanData[0].PresenterConfig, opaSessionObj)
|
||||
} else if opaSessionObj != nil {
|
||||
err = printConfigurationsScanning(opaSessionObj, ctx, jp)
|
||||
} else if imageScanData != nil {
|
||||
err = jp.PrintImageScan(ctx, imageScanData)
|
||||
err = jp.PrintImageScan(ctx, imageScanData[0].PresenterConfig)
|
||||
} else {
|
||||
err = fmt.Errorf("failed to print results, no data provided")
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ func (jp *JunitPrinter) PrintNextSteps() {
|
||||
|
||||
}
|
||||
|
||||
func (jp *JunitPrinter) ActionPrint(ctx context.Context, opaSessionObj *cautils.OPASessionObj, imageScanData *models.PresenterConfig) {
|
||||
func (jp *JunitPrinter) ActionPrint(ctx context.Context, opaSessionObj *cautils.OPASessionObj, imageScanData []cautils.ImageScanData) {
|
||||
junitResult := testsSuites(opaSessionObj)
|
||||
postureReportStr, err := xml.Marshal(junitResult)
|
||||
if err != nil {
|
||||
|
||||
@@ -93,7 +93,7 @@ func (pp *PdfPrinter) PrintNextSteps() {
|
||||
|
||||
}
|
||||
|
||||
func (pp *PdfPrinter) ActionPrint(ctx context.Context, opaSessionObj *cautils.OPASessionObj, imageScanData *models.PresenterConfig) {
|
||||
func (pp *PdfPrinter) ActionPrint(ctx context.Context, opaSessionObj *cautils.OPASessionObj, imageScanData []cautils.ImageScanData) {
|
||||
sortedControlIDs := getSortedControlsIDs(opaSessionObj.Report.SummaryDetails.Controls)
|
||||
|
||||
infoToPrintInfo := mapInfoToPrintInfo(opaSessionObj.Report.SummaryDetails.Controls)
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
v5 "github.com/anchore/grype/grype/db/v5"
|
||||
"github.com/anchore/grype/grype/presenter/models"
|
||||
"github.com/enescakir/emoji"
|
||||
logger "github.com/kubescape/go-logger"
|
||||
@@ -18,7 +17,6 @@ import (
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer"
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer/v2/prettyprinter"
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/imageprinter"
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils"
|
||||
"github.com/kubescape/opa-utils/objectsenvelopes"
|
||||
"github.com/kubescape/opa-utils/reporthandling/apis"
|
||||
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
|
||||
@@ -72,74 +70,39 @@ func (pp *PrettyPrinter) SetMainPrinter() {
|
||||
}
|
||||
}
|
||||
|
||||
func (pp *PrettyPrinter) convertToImageScanSummary(presenterConfig *models.PresenterConfig) (*imageprinter.ImageScanSummary, error) {
|
||||
doc, err := models.NewDocument(presenterConfig.Packages, presenterConfig.Context, presenterConfig.Matches, presenterConfig.IgnoredMatches, presenterConfig.MetadataProvider, nil, presenterConfig.DBStatus)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// convertToImageScanSummary takes a list of image scan data and converts it to a single image scan summary
|
||||
func (pp *PrettyPrinter) convertToImageScanSummary(imageScanData []cautils.ImageScanData) (*imageprinter.ImageScanSummary, error) {
|
||||
imageScanSummary := imageprinter.ImageScanSummary{
|
||||
CVEs: []imageprinter.CVE{},
|
||||
PackageScores: map[string]*imageprinter.PackageScore{},
|
||||
MapsSeverityToSummary: map[string]*imageprinter.SeveritySummary{},
|
||||
}
|
||||
|
||||
cves := extractCVEs(doc)
|
||||
for _, imageScan := range imageScanData {
|
||||
imageScanSummary.Images = append(imageScanSummary.Images, imageScan.Image)
|
||||
|
||||
mapPackageNameToScore := extractPkgNameToScore(doc)
|
||||
presenterConfig := imageScan.PresenterConfig
|
||||
doc, err := models.NewDocument(presenterConfig.Packages, presenterConfig.Context, presenterConfig.Matches, presenterConfig.IgnoredMatches, presenterConfig.MetadataProvider, nil, presenterConfig.DBStatus)
|
||||
if err != nil {
|
||||
logger.L().Error(fmt.Sprintf("failed to create document for image: %v", imageScan.Image), helpers.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
mapSeverityToSummary := extractSeverityToSummaryMap(cves)
|
||||
cves := extractCVEs(doc)
|
||||
imageScanSummary.CVEs = append(imageScanSummary.CVEs, cves...)
|
||||
|
||||
imageScanSummary := imageprinter.ImageScanSummary{
|
||||
CVEs: cves,
|
||||
MapsSeverityToSummary: mapSeverityToSummary,
|
||||
PackageScores: mapPackageNameToScore,
|
||||
mapPackageNameToScore := extractPkgNameToScore(doc)
|
||||
insertPackageScoresIntoMap(mapPackageNameToScore, imageScanSummary)
|
||||
|
||||
mapSeverityToSummary := extractSeverityToSummaryMap(cves)
|
||||
insertSeveritiesSummariesIntoMap(mapSeverityToSummary, imageScanSummary)
|
||||
}
|
||||
|
||||
return &imageScanSummary, nil
|
||||
}
|
||||
|
||||
func extractSeverityToSummaryMap(cves []imageprinter.CVE) map[string]*imageprinter.SeveritySummary {
|
||||
mapSeverityToSummary := map[string]*imageprinter.SeveritySummary{}
|
||||
for _, cve := range cves {
|
||||
if _, ok := mapSeverityToSummary[cve.Severity]; !ok {
|
||||
mapSeverityToSummary[cve.Severity] = &imageprinter.SeveritySummary{}
|
||||
}
|
||||
mapSeverityToSummary[cve.Severity].NumberOfCVEs = mapSeverityToSummary[cve.Severity].NumberOfCVEs + 1
|
||||
if cve.FixedState == string(v5.FixedState) {
|
||||
mapSeverityToSummary[cve.Severity].NumberOfFixableCVEs = mapSeverityToSummary[cve.Severity].NumberOfFixableCVEs + 1
|
||||
}
|
||||
}
|
||||
return mapSeverityToSummary
|
||||
}
|
||||
|
||||
func extractPkgNameToScore(doc models.Document) map[string]*imageprinter.Package {
|
||||
mapPackageNameToScore := make(map[string]*imageprinter.Package, 0)
|
||||
for _, cve := range doc.Matches {
|
||||
if _, ok := mapPackageNameToScore[cve.Artifact.Name]; !ok {
|
||||
mapPackageNameToScore[cve.Artifact.Name] = &imageprinter.Package{
|
||||
Score: 0,
|
||||
}
|
||||
}
|
||||
mapPackageNameToScore[cve.Artifact.Name].Score = mapPackageNameToScore[cve.Artifact.Name].Score + utils.ImageSeverityToInt(cve.Vulnerability.Severity)
|
||||
mapPackageNameToScore[cve.Artifact.Name].Version = cve.Artifact.Version
|
||||
}
|
||||
return mapPackageNameToScore
|
||||
}
|
||||
|
||||
func extractCVEs(doc models.Document) []imageprinter.CVE {
|
||||
cves := []imageprinter.CVE{}
|
||||
for _, match := range doc.Matches {
|
||||
cve := imageprinter.CVE{
|
||||
ID: match.Vulnerability.ID,
|
||||
Severity: match.Vulnerability.Severity,
|
||||
Package: match.Artifact.Name,
|
||||
Version: match.Artifact.Version,
|
||||
FixVersions: match.Vulnerability.Fix.Versions,
|
||||
FixedState: match.Vulnerability.Fix.State,
|
||||
}
|
||||
cves = append(cves, cve)
|
||||
}
|
||||
return cves
|
||||
}
|
||||
|
||||
func (pp *PrettyPrinter) PrintImageScan(ctx context.Context, presenterConfig *models.PresenterConfig) {
|
||||
imageScanSummary, err := pp.convertToImageScanSummary(presenterConfig)
|
||||
func (pp *PrettyPrinter) PrintImageScan(imageScanData []cautils.ImageScanData) {
|
||||
imageScanSummary, err := pp.convertToImageScanSummary(imageScanData)
|
||||
if err != nil {
|
||||
logger.L().Error("failed to convert to image scan summary", helpers.Error(err))
|
||||
return
|
||||
@@ -147,7 +110,7 @@ func (pp *PrettyPrinter) PrintImageScan(ctx context.Context, presenterConfig *mo
|
||||
pp.mainPrinter.PrintImageScanning(imageScanSummary)
|
||||
}
|
||||
|
||||
func (pp *PrettyPrinter) ActionPrint(_ context.Context, opaSessionObj *cautils.OPASessionObj, imageScanData *models.PresenterConfig) {
|
||||
func (pp *PrettyPrinter) ActionPrint(_ context.Context, opaSessionObj *cautils.OPASessionObj, imageScanData []cautils.ImageScanData) {
|
||||
if opaSessionObj != nil {
|
||||
fmt.Fprintf(pp.writer, "\n"+getSeparator("^")+"\n")
|
||||
|
||||
@@ -173,8 +136,8 @@ func (pp *PrettyPrinter) ActionPrint(_ context.Context, opaSessionObj *cautils.O
|
||||
pp.printAttackTracks(opaSessionObj)
|
||||
}
|
||||
|
||||
if imageScanData != nil {
|
||||
pp.PrintImageScan(context.Background(), imageScanData)
|
||||
if len(imageScanData) > 0 {
|
||||
pp.PrintImageScan(imageScanData)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ func (cp *ClusterPrinter) PrintImageScanning(summary *imageprinter.ImageScanSumm
|
||||
|
||||
func (cp *ClusterPrinter) PrintConfigurationsScanning(summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) {
|
||||
|
||||
cp.categoriesTablePrinter.PrintCategoriesTable(cp.writer, summaryDetails, sortedControlIDs)
|
||||
cp.categoriesTablePrinter.PrintCategoriesTables(cp.writer, summaryDetails, sortedControlIDs)
|
||||
|
||||
printComplianceScore(cp.writer, filterComplianceFrameworks(summaryDetails.ListFrameworks()))
|
||||
|
||||
@@ -44,6 +44,13 @@ func (cp *ClusterPrinter) PrintNextSteps() {
|
||||
printNextSteps(cp.writer, cp.getNextSteps())
|
||||
}
|
||||
|
||||
func (cp *ClusterPrinter) getNextSteps() []string {
|
||||
return []string{
|
||||
complianceScanRunText,
|
||||
installHelmText,
|
||||
}
|
||||
}
|
||||
|
||||
func (cp *ClusterPrinter) printTopWorkloads(summaryDetails *reportsummary.SummaryDetails) {
|
||||
cautils.InfoTextDisplay(cp.writer, getTopWorkloadsTitle(len(summaryDetails.TopWorkloadsByScore)))
|
||||
|
||||
@@ -54,16 +61,11 @@ func (cp *ClusterPrinter) printTopWorkloads(summaryDetails *reportsummary.Summar
|
||||
cautils.SimpleDisplay(cp.writer, fmt.Sprintf("%d. namespace: %s, name: %s, kind: %s - '%s'\n", i+1, ns, name, kind, cp.getWorkloadScanCommand(ns, kind, name)))
|
||||
}
|
||||
|
||||
cautils.SimpleDisplay(cp.writer, "Read more about the most risky workloads here: https://docs.io/most-risky-workloads\n")
|
||||
|
||||
cautils.InfoTextDisplay(cp.writer, "\n")
|
||||
}
|
||||
|
||||
func (cp *ClusterPrinter) getWorkloadScanCommand(namespace, kind, name string) string {
|
||||
return fmt.Sprintf("$ kubescape scan workload %s/%s/%s", namespace, kind, name)
|
||||
}
|
||||
|
||||
func (cp *ClusterPrinter) getNextSteps() []string {
|
||||
return []string{
|
||||
complianceScanRunText,
|
||||
installHelmText,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
type MainPrinter interface {
|
||||
PrintConfigurationsScanning(*reportsummary.SummaryDetails, [][]string)
|
||||
PrintImageScanning(*imageprinter.ImageScanSummary)
|
||||
PrintConfigurationsScanning(summaryDetails *reportsummary.SummaryDetails, sortedControls [][]string)
|
||||
PrintImageScanning(imageScanSummary *imageprinter.ImageScanSummary)
|
||||
PrintNextSteps()
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package prettyprinter
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter"
|
||||
@@ -27,13 +26,15 @@ func NewRepoPrinter(writer *os.File, inputPatterns []string) *RepoPrinter {
|
||||
|
||||
var _ MainPrinter = &RepoPrinter{}
|
||||
|
||||
func (rp *RepoPrinter) PrintImageScanning(*imageprinter.ImageScanSummary) {
|
||||
func (rp *RepoPrinter) PrintImageScanning(summary *imageprinter.ImageScanSummary) {
|
||||
printImageScanningSummary(rp.writer, *summary, false)
|
||||
printTopVulnerabilities(rp.writer, *summary)
|
||||
}
|
||||
|
||||
func (rp *RepoPrinter) PrintConfigurationsScanning(summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) {
|
||||
rp.categoriesTablePrinter.PrintCategoriesTable(rp.writer, summaryDetails, sortedControlIDs)
|
||||
rp.categoriesTablePrinter.PrintCategoriesTables(rp.writer, summaryDetails, sortedControlIDs)
|
||||
|
||||
if len(summaryDetails.TopWorkloadsByScore) > 0 {
|
||||
if len(summaryDetails.TopWorkloadsByScore) > 1 {
|
||||
rp.printTopWorkloads(summaryDetails)
|
||||
}
|
||||
|
||||
@@ -52,10 +53,6 @@ func (rp *RepoPrinter) getNextSteps() []string {
|
||||
}
|
||||
}
|
||||
|
||||
func (rp *RepoPrinter) printNextSteps() {
|
||||
printNextSteps(rp.writer, rp.getNextSteps())
|
||||
}
|
||||
|
||||
func (rp *RepoPrinter) printTopWorkloads(summaryDetails *reportsummary.SummaryDetails) {
|
||||
cautils.InfoTextDisplay(rp.writer, getTopWorkloadsTitle(len(summaryDetails.TopWorkloadsByScore)))
|
||||
|
||||
@@ -75,14 +72,10 @@ func (rp *RepoPrinter) getWorkloadScanCommand(ns, kind, name string, source repo
|
||||
if ns == "" {
|
||||
cmd = fmt.Sprintf("$ kubescape scan workload %s/%s", kind, name)
|
||||
}
|
||||
if source.FileType == "Helm" {
|
||||
return fmt.Sprintf("%s --chart-path=%s", cmd, source.RelativePath)
|
||||
if source.FileType == reporthandling.SourceTypeHelmChart {
|
||||
return fmt.Sprintf("%s --chart-path=%s --file-path=%s", cmd, source.Path, source.RelativePath)
|
||||
|
||||
} else {
|
||||
return fmt.Sprintf("%s --file-path=%s", cmd, source.RelativePath)
|
||||
}
|
||||
}
|
||||
|
||||
func (rp *RepoPrinter) generateTableNextSteps(controlSummary reportsummary.IControlSummary, inputPatterns []string) string {
|
||||
return fmt.Sprintf("$ kubescape scan control %s %s", controlSummary.GetID(), strings.Join(inputPatterns, ","))
|
||||
}
|
||||
|
||||
@@ -1,31 +1,110 @@
|
||||
package configurationprinter
|
||||
|
||||
import (
|
||||
"github.com/kubescape/opa-utils/reporthandling/apis"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils"
|
||||
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
)
|
||||
|
||||
const (
|
||||
categoriesColumnSeverity = iota
|
||||
categoriesColumnName = iota
|
||||
categoriesColumnFailed = iota
|
||||
categoriesColumnNextSteps = iota
|
||||
docsPrefix = "https://hub.armosec.io/docs"
|
||||
scanControlPrefix = "$ kubescape scan control"
|
||||
controlNameHeader = "CONTROL NAME"
|
||||
statusHeader = "STATUS"
|
||||
docsHeader = "DOCS"
|
||||
resourcesHeader = "RESOURCES"
|
||||
runHeader = "RUN"
|
||||
)
|
||||
|
||||
func setCategoryStatusRow(controlSummary reportsummary.IControlSummary, row []string) {
|
||||
status := controlSummary.GetStatus().Status()
|
||||
if status == apis.StatusSkipped {
|
||||
status = "action required"
|
||||
// initializes the table headers and column alignments based on the category type
|
||||
func initCategoryTableData(categoryType CategoryType) ([]string, []int) {
|
||||
if categoryType == TypeCounting {
|
||||
return getCategoryCountingTypeHeaders(), getCountingTypeAlignments()
|
||||
}
|
||||
row[categoriesColumnFailed] = string(status)
|
||||
return getCategoryStatusTypeHeaders(), getStatusTypeAlignments()
|
||||
}
|
||||
|
||||
func getCommonCategoriesTableHeaders() []string {
|
||||
headers := make([]string, 4)
|
||||
headers[categoriesColumnSeverity] = "SEVERITY"
|
||||
headers[categoriesColumnName] = "CONTROL NAME"
|
||||
headers[categoriesColumnFailed] = "STATUS"
|
||||
headers[categoriesColumnNextSteps] = "NEXT STEP"
|
||||
func getCategoryStatusTypeHeaders() []string {
|
||||
headers := make([]string, 3)
|
||||
headers[0] = controlNameHeader
|
||||
headers[1] = statusHeader
|
||||
headers[2] = docsHeader
|
||||
|
||||
return headers
|
||||
}
|
||||
|
||||
func getCategoryCountingTypeHeaders() []string {
|
||||
headers := make([]string, 3)
|
||||
headers[0] = controlNameHeader
|
||||
headers[1] = resourcesHeader
|
||||
headers[2] = runHeader
|
||||
|
||||
return headers
|
||||
}
|
||||
|
||||
func getStatusTypeAlignments() []int {
|
||||
return []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER}
|
||||
}
|
||||
|
||||
func getCountingTypeAlignments() []int {
|
||||
return []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT}
|
||||
}
|
||||
|
||||
// returns a row for status type table based on the control summary
|
||||
func generateCategoryStatusRow(controlSummary reportsummary.IControlSummary, infoToPrintInfo []utils.InfoStars) []string {
|
||||
|
||||
// show only passed, failed and action required controls
|
||||
status := controlSummary.GetStatus()
|
||||
if !status.IsFailed() && !status.IsSkipped() && !status.IsPassed() {
|
||||
return nil
|
||||
}
|
||||
|
||||
rows := make([]string, 3)
|
||||
|
||||
rows[0] = controlSummary.GetName()
|
||||
if len(controlSummary.GetName()) > 50 {
|
||||
rows[0] = controlSummary.GetName()[:50] + "..."
|
||||
} else {
|
||||
rows[0] = controlSummary.GetName()
|
||||
}
|
||||
|
||||
// skipped is shown as action required
|
||||
if status.IsSkipped() {
|
||||
rows[1] = fmt.Sprintf("%s %s", "action required", GetInfoColumn(controlSummary, infoToPrintInfo))
|
||||
} else {
|
||||
rows[1] = string(controlSummary.GetStatus().Status())
|
||||
}
|
||||
|
||||
rows[2] = getDocsForControl(controlSummary)
|
||||
|
||||
return rows
|
||||
|
||||
}
|
||||
|
||||
func getCategoryTableWriter(writer io.Writer, headers []string, columnAligments []int) *tablewriter.Table {
|
||||
table := tablewriter.NewWriter(writer)
|
||||
table.SetHeader(headers)
|
||||
table.SetHeaderLine(true)
|
||||
table.SetColumnAlignment(columnAligments)
|
||||
table.SetAutoWrapText(false)
|
||||
return table
|
||||
}
|
||||
|
||||
func renderSingleCategory(writer io.Writer, categoryName string, table *tablewriter.Table, rows [][]string, infoToPrintInfo []utils.InfoStars) {
|
||||
cautils.InfoTextDisplay(writer, "\n"+categoryName+"\n")
|
||||
|
||||
table.ClearRows()
|
||||
table.AppendBulk(rows)
|
||||
|
||||
table.Render()
|
||||
|
||||
if len(infoToPrintInfo) > 0 {
|
||||
utils.PrintInfo(writer, infoToPrintInfo)
|
||||
}
|
||||
|
||||
cautils.SimpleDisplay(writer, "\n")
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils"
|
||||
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
|
||||
)
|
||||
|
||||
@@ -19,58 +20,63 @@ func (cp *ClusterPrinter) PrintSummaryTable(writer io.Writer, summaryDetails *re
|
||||
|
||||
}
|
||||
|
||||
func (cp *ClusterPrinter) PrintCategoriesTable(writer io.Writer, summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) {
|
||||
headers := cp.getCategoriesTableHeaders()
|
||||
columnAligments := cp.getCategoriesColumnsAlignments()
|
||||
func (cp *ClusterPrinter) PrintCategoriesTables(writer io.Writer, summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) {
|
||||
|
||||
table := getTableWriter(writer, headers, columnAligments)
|
||||
categoriesToCategoryControls := mapCategoryToSummary(summaryDetails.ListControls(), mapClusterControlsToCategories)
|
||||
|
||||
mapCategoryToRows := cp.generateRows(summaryDetails, sortedControlIDs)
|
||||
for _, id := range categoriesDisplayOrder {
|
||||
categoryControl, ok := categoriesToCategoryControls[id]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
renderCategoriesTable(mapCategoryToRows, writer, table)
|
||||
infoToPrintInfo := utils.MapInfoToPrintInfoFromIface(categoryControl.controlSummaries)
|
||||
|
||||
cp.renderSingleCategoryTable(categoryControl.CategoryName, mapCategoryToType[id], writer, categoryControl.controlSummaries, infoToPrintInfo)
|
||||
}
|
||||
}
|
||||
|
||||
func (cp *ClusterPrinter) generateRows(summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) map[string][][]string {
|
||||
mapCategoryToRows := make(map[string][][]string)
|
||||
func (cp *ClusterPrinter) renderSingleCategoryTable(categoryName string, categoryType CategoryType, writer io.Writer, controlSummaries []reportsummary.IControlSummary, infoToPrintInfo []utils.InfoStars) {
|
||||
headers, columnAligments := initCategoryTableData(categoryType)
|
||||
|
||||
categoriesToControlSummariesMap := mapCategoryToControlSummaries(*summaryDetails, sortedControlIDs)
|
||||
table := getCategoryTableWriter(writer, headers, columnAligments)
|
||||
|
||||
for category, ctrls := range categoriesToControlSummariesMap {
|
||||
for i := range ctrls {
|
||||
row := cp.generateCategoriesRow(ctrls[i])
|
||||
if len(row) > 0 {
|
||||
mapCategoryToRows[category] = append(mapCategoryToRows[category], row)
|
||||
}
|
||||
var rows [][]string
|
||||
for _, ctrls := range controlSummaries {
|
||||
var row []string
|
||||
if categoryType == TypeCounting {
|
||||
row = cp.generateCountingCategoryRow(ctrls)
|
||||
} else {
|
||||
row = generateCategoryStatusRow(ctrls, infoToPrintInfo)
|
||||
}
|
||||
if len(row) > 0 {
|
||||
rows = append(rows, row)
|
||||
}
|
||||
}
|
||||
|
||||
return mapCategoryToRows
|
||||
}
|
||||
|
||||
func (cp *ClusterPrinter) generateCategoriesRow(controlSummary reportsummary.IControlSummary) []string {
|
||||
row := make([]string, 4)
|
||||
|
||||
row[categoriesColumnSeverity] = GetSeverityColumn(controlSummary)
|
||||
|
||||
if len(controlSummary.GetName()) > 50 {
|
||||
row[categoriesColumnName] = controlSummary.GetName()[:50] + "..."
|
||||
} else {
|
||||
row[categoriesColumnName] = controlSummary.GetName()
|
||||
if len(rows) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
setCategoryStatusRow(controlSummary, row)
|
||||
renderSingleCategory(writer, categoryName, table, rows, infoToPrintInfo)
|
||||
|
||||
row[categoriesColumnNextSteps] = cp.generateNextSteps(controlSummary)
|
||||
}
|
||||
|
||||
func (cp *ClusterPrinter) generateCountingCategoryRow(controlSummary reportsummary.IControlSummary) []string {
|
||||
|
||||
row := make([]string, 3)
|
||||
|
||||
row[0] = controlSummary.GetName()
|
||||
|
||||
row[1] = fmt.Sprintf("%d", controlSummary.NumberOfResources().Failed())
|
||||
|
||||
row[2] = cp.generateTableNextSteps(controlSummary)
|
||||
|
||||
return row
|
||||
}
|
||||
|
||||
func (cp *ClusterPrinter) getCategoriesTableHeaders() []string {
|
||||
return getCommonCategoriesTableHeaders()
|
||||
}
|
||||
|
||||
func (cp *ClusterPrinter) getCategoriesColumnsAlignments() []int {
|
||||
return getCommonColumnsAlignments()
|
||||
func (cp *ClusterPrinter) generateTableNextSteps(controlSummary reportsummary.IControlSummary) string {
|
||||
return fmt.Sprintf("%s %s -v", scanControlPrefix, controlSummary.GetID())
|
||||
}
|
||||
|
||||
func (cp *ClusterPrinter) generateNextSteps(controlSummary reportsummary.IControlSummary) string {
|
||||
|
||||
@@ -66,7 +66,7 @@ func (fp *FrameworkPrinter) PrintSummaryTable(writer io.Writer, summaryDetails *
|
||||
utils.PrintInfo(writer, infoToPrintInfo)
|
||||
}
|
||||
|
||||
func (fp *FrameworkPrinter) PrintCategoriesTable(writer io.Writer, summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) {
|
||||
func (fp *FrameworkPrinter) PrintCategoriesTables(writer io.Writer, summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,6 @@ import (
|
||||
)
|
||||
|
||||
type TablePrinter interface {
|
||||
PrintCategoriesTable(writer io.Writer, summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string)
|
||||
PrintCategoriesTables(writer io.Writer, summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string)
|
||||
PrintSummaryTable(writer io.Writer, summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils"
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
|
||||
)
|
||||
@@ -25,58 +26,58 @@ func (rp *RepoPrinter) PrintSummaryTable(writer io.Writer, summaryDetails *repor
|
||||
|
||||
}
|
||||
|
||||
func (rp *RepoPrinter) PrintCategoriesTable(writer io.Writer, summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) {
|
||||
headers := rp.getCategoriesTableHeaders()
|
||||
columnAligments := rp.getCategoriesColumnsAlignments()
|
||||
func (rp *RepoPrinter) PrintCategoriesTables(writer io.Writer, summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) {
|
||||
|
||||
table := getTableWriter(writer, headers, columnAligments)
|
||||
categoriesToCategoryControls := mapCategoryToSummary(summaryDetails.ListControls(), mapClusterControlsToCategories)
|
||||
|
||||
mapCategoryToRows := rp.generateRows(summaryDetails, sortedControlIDs)
|
||||
for _, id := range categoriesDisplayOrder {
|
||||
categoryControl, ok := categoriesToCategoryControls[id]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
renderCategoriesTable(mapCategoryToRows, writer, table)
|
||||
infoToPrintInfo := utils.MapInfoToPrintInfoFromIface(categoryControl.controlSummaries)
|
||||
|
||||
rp.renderSingleCategoryTable(categoryControl.CategoryName, mapCategoryToType[id], writer, categoryControl.controlSummaries, infoToPrintInfo)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (rp *RepoPrinter) generateRows(summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) map[string][][]string {
|
||||
mapCategoryToRows := make(map[string][][]string)
|
||||
func (rp *RepoPrinter) renderSingleCategoryTable(categoryName string, categoryType CategoryType, writer io.Writer, controlSummaries []reportsummary.IControlSummary, infoToPrintInfo []utils.InfoStars) {
|
||||
headers, columnAligments := initCategoryTableData(categoryType)
|
||||
|
||||
categoriesToControlSummariesMap := mapCategoryToControlSummaries(*summaryDetails, sortedControlIDs)
|
||||
table := getCategoryTableWriter(writer, headers, columnAligments)
|
||||
|
||||
for category, ctrls := range categoriesToControlSummariesMap {
|
||||
for i := range ctrls {
|
||||
row := rp.generateCategoriesRow(ctrls[i], rp.inputPatterns)
|
||||
if len(row) > 0 {
|
||||
mapCategoryToRows[category] = append(mapCategoryToRows[category], row)
|
||||
}
|
||||
var rows [][]string
|
||||
for _, ctrls := range controlSummaries {
|
||||
var row []string
|
||||
if categoryType == TypeCounting {
|
||||
row = rp.generateCountingCategoryRow(ctrls, rp.inputPatterns)
|
||||
} else {
|
||||
row = generateCategoryStatusRow(ctrls, infoToPrintInfo)
|
||||
}
|
||||
if len(row) > 0 {
|
||||
rows = append(rows, row)
|
||||
}
|
||||
}
|
||||
|
||||
return mapCategoryToRows
|
||||
}
|
||||
|
||||
func (rp *RepoPrinter) generateCategoriesRow(controlSummary reportsummary.IControlSummary, inputPatterns []string) []string {
|
||||
row := make([]string, 4)
|
||||
row[categoriesColumnSeverity] = GetSeverityColumn(controlSummary)
|
||||
|
||||
if len(controlSummary.GetName()) > 50 {
|
||||
row[categoriesColumnName] = controlSummary.GetName()[:50] + "..."
|
||||
} else {
|
||||
row[categoriesColumnName] = controlSummary.GetName()
|
||||
if len(rows) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
setCategoryStatusRow(controlSummary, row)
|
||||
|
||||
row[categoriesColumnNextSteps] = rp.generateTableNextSteps(controlSummary, inputPatterns)
|
||||
|
||||
return row
|
||||
renderSingleCategory(writer, categoryName, table, rows, infoToPrintInfo)
|
||||
}
|
||||
|
||||
func (rp *RepoPrinter) getCategoriesTableHeaders() []string {
|
||||
return getCommonCategoriesTableHeaders()
|
||||
}
|
||||
func (rp *RepoPrinter) generateCountingCategoryRow(controlSummary reportsummary.IControlSummary, inputPatterns []string) []string {
|
||||
rows := make([]string, 3)
|
||||
|
||||
func (rp *RepoPrinter) getCategoriesColumnsAlignments() []int {
|
||||
return getCommonColumnsAlignments()
|
||||
rows[0] = controlSummary.GetName()
|
||||
|
||||
rows[1] = fmt.Sprintf("%d", controlSummary.NumberOfResources().Failed())
|
||||
|
||||
rows[2] = rp.generateTableNextSteps(controlSummary, inputPatterns)
|
||||
|
||||
return rows
|
||||
}
|
||||
|
||||
func (rp *RepoPrinter) getWorkloadScanCommand(ns, kind, name string, source reporthandling.Source) string {
|
||||
|
||||
@@ -3,8 +3,10 @@ package configurationprinter
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils"
|
||||
"github.com/kubescape/opa-utils/reporthandling/apis"
|
||||
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
)
|
||||
@@ -18,6 +20,14 @@ const (
|
||||
_summaryRowLen = iota
|
||||
)
|
||||
|
||||
func ControlCountersForSummary(counters reportsummary.ICounters) string {
|
||||
return fmt.Sprintf("Controls: %d (Failed: %d, Passed: %d, Action Required: %d)", counters.All(), counters.Failed(), counters.Passed(), counters.Skipped())
|
||||
}
|
||||
|
||||
func GetSeverityColumn(controlSummary reportsummary.IControlSummary) string {
|
||||
return color.New(utils.GetColor(apis.ControlSeverityToInt(controlSummary.GetScoreFactor())), color.Bold).SprintFunc()(apis.ControlSeverityToString(controlSummary.GetScoreFactor()))
|
||||
}
|
||||
|
||||
func GetControlTableHeaders() []string {
|
||||
headers := make([]string, _summaryRowLen)
|
||||
headers[summaryColumnName] = "CONTROL NAME"
|
||||
|
||||
@@ -2,68 +2,70 @@ package configurationprinter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils"
|
||||
"github.com/kubescape/opa-utils/reporthandling/apis"
|
||||
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
)
|
||||
|
||||
func GetSeverityColumn(controlSummary reportsummary.IControlSummary) string {
|
||||
return color.New(utils.GetColor(apis.ControlSeverityToInt(controlSummary.GetScoreFactor())), color.Bold).SprintFunc()(apis.ControlSeverityToString(controlSummary.GetScoreFactor()))
|
||||
}
|
||||
// returns map of category ID to category controls (name and controls)
|
||||
// controls will be on the map only if the are in the mapClusterControlsToCategories map
|
||||
func mapCategoryToSummary(controlSummaries []reportsummary.IControlSummary, mapDisplayCtrlIDToCategory map[string]string) map[string]CategoryControls {
|
||||
|
||||
type InfoStars struct {
|
||||
Stars string
|
||||
Info string
|
||||
}
|
||||
mapCategoriesToCtrlSummary := map[string][]reportsummary.IControlSummary{}
|
||||
// helper map to get the category name
|
||||
mapCategoryIDToName := make(map[string]string)
|
||||
|
||||
func ControlCountersForSummary(counters reportsummary.ICounters) string {
|
||||
return fmt.Sprintf("Controls: %d (Failed: %d, Passed: %d, Action Required: %d)", counters.All(), counters.Failed(), counters.Passed(), counters.Skipped())
|
||||
}
|
||||
for i := range controlSummaries {
|
||||
// check if we need to print this control
|
||||
category, ok := mapDisplayCtrlIDToCategory[controlSummaries[i].GetID()]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
func getCommonColumnsAlignments() []int {
|
||||
return []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT}
|
||||
}
|
||||
|
||||
func mapCategoryToControlSummaries(summaryDetails reportsummary.SummaryDetails, sortedControlIDs [][]string) map[string][]reportsummary.IControlSummary {
|
||||
categories := map[string][]reportsummary.IControlSummary{}
|
||||
|
||||
for i := len(sortedControlIDs) - 1; i >= 0; i-- {
|
||||
for _, c := range sortedControlIDs[i] {
|
||||
ctrl := summaryDetails.Controls.GetControl(reportsummary.EControlCriteriaID, c)
|
||||
if ctrl.GetStatus().Status() == apis.StatusPassed {
|
||||
continue
|
||||
// the category on the map can be either category or subcategory, so we need to check both
|
||||
if controlSummaries[i].GetCategory().ID == category {
|
||||
if _, ok := mapCategoriesToCtrlSummary[controlSummaries[i].GetCategory().ID]; !ok {
|
||||
mapCategoryIDToName[controlSummaries[i].GetCategory().ID] = controlSummaries[i].GetCategory().Name // set category name
|
||||
mapCategoriesToCtrlSummary[controlSummaries[i].GetCategory().ID] = []reportsummary.IControlSummary{}
|
||||
}
|
||||
for j := range ctrl.GetCategories() {
|
||||
categories[ctrl.GetCategories()[j]] = append(categories[ctrl.GetCategories()[j]], ctrl)
|
||||
mapCategoriesToCtrlSummary[controlSummaries[i].GetCategory().ID] = append(mapCategoriesToCtrlSummary[controlSummaries[i].GetCategory().ID], controlSummaries[i])
|
||||
continue
|
||||
}
|
||||
|
||||
if controlSummaries[i].GetCategory().SubCategory.ID == category {
|
||||
if _, ok := mapCategoriesToCtrlSummary[controlSummaries[i].GetCategory().SubCategory.ID]; !ok {
|
||||
mapCategoryIDToName[controlSummaries[i].GetCategory().SubCategory.ID] = controlSummaries[i].GetCategory().SubCategory.Name // set category name
|
||||
mapCategoriesToCtrlSummary[controlSummaries[i].GetCategory().SubCategory.ID] = []reportsummary.IControlSummary{}
|
||||
}
|
||||
mapCategoriesToCtrlSummary[controlSummaries[i].GetCategory().SubCategory.ID] = append(mapCategoriesToCtrlSummary[controlSummaries[i].GetCategory().SubCategory.ID], controlSummaries[i])
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return categories
|
||||
mapCategoryToControls := buildCategoryToControlsMap(mapCategoriesToCtrlSummary, mapCategoryIDToName)
|
||||
|
||||
return mapCategoryToControls
|
||||
}
|
||||
|
||||
func getTableWriter(writer io.Writer, headers []string, columnAligments []int) *tablewriter.Table {
|
||||
table := tablewriter.NewWriter(writer)
|
||||
table.SetHeader(headers)
|
||||
table.SetHeaderLine(true)
|
||||
table.SetColumnAlignment(columnAligments)
|
||||
return table
|
||||
}
|
||||
|
||||
func renderCategoriesTable(mapCategoryToRows map[string][][]string, writer io.Writer, table *tablewriter.Table) {
|
||||
for category, rows := range mapCategoryToRows {
|
||||
cautils.InfoTextDisplay(writer, "\n"+category+"\n")
|
||||
|
||||
table.ClearRows()
|
||||
table.AppendBulk(rows)
|
||||
|
||||
table.Render()
|
||||
|
||||
cautils.SimpleDisplay(writer, "\n")
|
||||
// returns map of category ID to category controls (name and controls)
|
||||
func buildCategoryToControlsMap(mapCategoriesToCtrlSummary map[string][]reportsummary.IControlSummary, mapCategoryIDToName map[string]string) map[string]CategoryControls {
|
||||
mapCategoryToControls := make(map[string]CategoryControls)
|
||||
for categoryID, ctrls := range mapCategoriesToCtrlSummary {
|
||||
categoryName := mapCategoryIDToName[categoryID]
|
||||
mapCategoryToControls[categoryID] = CategoryControls{
|
||||
CategoryName: categoryName,
|
||||
controlSummaries: ctrls,
|
||||
}
|
||||
}
|
||||
return mapCategoryToControls
|
||||
}
|
||||
|
||||
// returns doc link for control
|
||||
func getDocsForControl(controlSummary reportsummary.IControlSummary) string {
|
||||
return fmt.Sprintf("%s/%s", docsPrefix, strings.ToLower(controlSummary.GetID()))
|
||||
}
|
||||
|
||||
// returns run command with verbose for control
|
||||
func getRunCommandForControl(controlSummary reportsummary.IControlSummary) string {
|
||||
return fmt.Sprintf("%s %s -v", scanControlPrefix, controlSummary.GetID())
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils"
|
||||
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
|
||||
)
|
||||
|
||||
@@ -20,59 +21,71 @@ func (wp *WorkloadPrinter) PrintSummaryTable(writer io.Writer, summaryDetails *r
|
||||
|
||||
}
|
||||
|
||||
func (wp *WorkloadPrinter) PrintCategoriesTable(writer io.Writer, summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) {
|
||||
func (wp *WorkloadPrinter) PrintCategoriesTables(writer io.Writer, summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) {
|
||||
|
||||
headers := wp.getCategoriesTableHeaders()
|
||||
columnAligments := wp.getCategoriesColumnsAlignments()
|
||||
categoriesToCategoryControls := mapCategoryToSummary(summaryDetails.ListControls(), mapClusterControlsToCategories)
|
||||
|
||||
table := getTableWriter(writer, headers, columnAligments)
|
||||
for _, id := range categoriesDisplayOrder {
|
||||
categoryControl, ok := categoriesToCategoryControls[id]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
mapCategoryToRows := wp.generateRows(summaryDetails, sortedControlIDs)
|
||||
infoToPrintInfo := utils.MapInfoToPrintInfoFromIface(categoryControl.controlSummaries)
|
||||
|
||||
renderCategoriesTable(mapCategoryToRows, writer, table)
|
||||
wp.renderSingleCategoryTable(categoryControl.CategoryName, mapCategoryToType[id], writer, categoryControl.controlSummaries, infoToPrintInfo)
|
||||
}
|
||||
}
|
||||
|
||||
func (wp *WorkloadPrinter) generateRows(summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) map[string][][]string {
|
||||
mapCategoryToRows := make(map[string][][]string)
|
||||
func (wp *WorkloadPrinter) renderSingleCategoryTable(categoryName string, categoryType CategoryType, writer io.Writer, controlSummaries []reportsummary.IControlSummary, infoToPrintInfo []utils.InfoStars) {
|
||||
headers, columnAligments := initCategoryTableData(categoryType)
|
||||
|
||||
categoriesToControlSummariesMap := mapCategoryToControlSummaries(*summaryDetails, sortedControlIDs)
|
||||
table := getCategoryTableWriter(writer, headers, columnAligments)
|
||||
|
||||
for category, ctrls := range categoriesToControlSummariesMap {
|
||||
for i := range ctrls {
|
||||
row := wp.generateCategoriesRow(ctrls[i])
|
||||
if len(row) > 0 {
|
||||
mapCategoryToRows[category] = append(mapCategoryToRows[category], row)
|
||||
}
|
||||
var rows [][]string
|
||||
for _, ctrls := range controlSummaries {
|
||||
var row []string
|
||||
if categoryType == TypeCounting {
|
||||
row = wp.generateCountingCategoryRow(ctrls)
|
||||
} else {
|
||||
row = generateCategoryStatusRow(ctrls, infoToPrintInfo)
|
||||
}
|
||||
if len(row) > 0 {
|
||||
rows = append(rows, row)
|
||||
}
|
||||
}
|
||||
|
||||
return mapCategoryToRows
|
||||
if len(rows) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
renderSingleCategory(writer, categoryName, table, rows, infoToPrintInfo)
|
||||
|
||||
}
|
||||
|
||||
func (wp *WorkloadPrinter) generateCountingCategoryRow(controlSummary reportsummary.IControlSummary) []string {
|
||||
|
||||
row := make([]string, 3)
|
||||
|
||||
row[0] = controlSummary.GetName()
|
||||
|
||||
row[1] = fmt.Sprintf("%d", controlSummary.NumberOfResources().Failed())
|
||||
|
||||
row[2] = wp.generateTableNextSteps(controlSummary)
|
||||
|
||||
return row
|
||||
}
|
||||
|
||||
func (wp *WorkloadPrinter) generateTableNextSteps(controlSummary reportsummary.IControlSummary) string {
|
||||
return fmt.Sprintf("%s %s -v", scanControlPrefix, controlSummary.GetID())
|
||||
}
|
||||
|
||||
func (wp *WorkloadPrinter) getCategoriesTableHeaders() []string {
|
||||
return getCommonCategoriesTableHeaders()
|
||||
return getCategoryCountingTypeHeaders()
|
||||
}
|
||||
|
||||
func (wp *WorkloadPrinter) getCategoriesColumnsAlignments() []int {
|
||||
return getCommonColumnsAlignments()
|
||||
}
|
||||
|
||||
func (wp *WorkloadPrinter) generateCategoriesRow(controlSummary reportsummary.IControlSummary) []string {
|
||||
row := make([]string, 4)
|
||||
|
||||
row[categoriesColumnSeverity] = GetSeverityColumn(controlSummary)
|
||||
|
||||
if len(controlSummary.GetName()) > 50 {
|
||||
row[categoriesColumnName] = controlSummary.GetName()[:50] + "..."
|
||||
} else {
|
||||
row[categoriesColumnName] = controlSummary.GetName()
|
||||
}
|
||||
|
||||
setCategoryStatusRow(controlSummary, row)
|
||||
|
||||
row[categoriesColumnNextSteps] = wp.generateNextSteps(controlSummary)
|
||||
|
||||
return row
|
||||
return getCountingTypeAlignments()
|
||||
}
|
||||
|
||||
func (wp *WorkloadPrinter) generateNextSteps(controlSummary reportsummary.IControlSummary) string {
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
package imageprinter
|
||||
|
||||
type ImageScanSummary struct {
|
||||
MapsSeverityToSummary map[string]*SeveritySummary
|
||||
CVEs []CVE
|
||||
PackageScores map[string]*Package
|
||||
}
|
||||
|
||||
type SeveritySummary struct {
|
||||
NumberOfCVEs int
|
||||
NumberOfFixableCVEs int
|
||||
}
|
||||
|
||||
type CVE struct {
|
||||
Severity string
|
||||
ID string
|
||||
Package string
|
||||
Version string
|
||||
FixVersions []string
|
||||
FixedState string
|
||||
}
|
||||
|
||||
type Package struct {
|
||||
Version string
|
||||
Score int
|
||||
}
|
||||
@@ -2,13 +2,6 @@ package imageprinter
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
v5 "github.com/anchore/grype/grype/db/v5"
|
||||
"github.com/fatih/color"
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -37,53 +30,5 @@ func (tw *TableWriter) PrintImageScanningTable(writer io.Writer, summary ImageSc
|
||||
headers := getImageScanningHeaders()
|
||||
columnAlignments := getImageScanningColumnsAlignments()
|
||||
|
||||
table := tablewriter.NewWriter(writer)
|
||||
table.SetHeader(headers)
|
||||
table.SetHeaderLine(true)
|
||||
table.SetColumnAlignment(columnAlignments)
|
||||
|
||||
table.AppendBulk(rows)
|
||||
|
||||
table.Render()
|
||||
}
|
||||
|
||||
func generateRows(summary ImageScanSummary) [][]string {
|
||||
rows := make([][]string, 0, len(summary.CVEs))
|
||||
|
||||
sort.Slice(summary.CVEs, func(i, j int) bool {
|
||||
return utils.ImageSeverityToInt(summary.CVEs[i].Severity) > utils.ImageSeverityToInt(summary.CVEs[j].Severity)
|
||||
})
|
||||
|
||||
for _, cve := range summary.CVEs {
|
||||
rows = append(rows, generateImageScanRow(cve))
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
func generateImageScanRow(cve CVE) []string {
|
||||
row := make([]string, 5)
|
||||
row[imageColumnSeverity] = color.New(utils.GetColor(utils.ImageSeverityToInt(cve.Severity)), color.Bold).Sprint(cve.Severity)
|
||||
row[imageColumnName] = cve.ID
|
||||
row[imageColumnComponent] = cve.Package
|
||||
row[imageColumnVersion] = cve.Version
|
||||
if cve.FixedState == string(v5.FixedState) {
|
||||
row[imageColumnFixedIn] = strings.Join(cve.FixVersions, "")
|
||||
} else if cve.FixedState == string(v5.WontFixState) {
|
||||
row[imageColumnFixedIn] = cve.FixedState
|
||||
}
|
||||
return row
|
||||
}
|
||||
|
||||
func getImageScanningHeaders() []string {
|
||||
headers := make([]string, 5)
|
||||
headers[imageColumnSeverity] = "SEVERITY"
|
||||
headers[imageColumnName] = "NAME"
|
||||
headers[imageColumnComponent] = "COMPONENT"
|
||||
headers[imageColumnVersion] = "VERSION"
|
||||
headers[imageColumnFixedIn] = "FIXED IN"
|
||||
return headers
|
||||
}
|
||||
|
||||
func getImageScanningColumnsAlignments() []int {
|
||||
return []int{tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT}
|
||||
renderTable(writer, headers, columnAlignments, rows)
|
||||
}
|
||||
|
||||
@@ -16,6 +16,25 @@ type InfoStars struct {
|
||||
Info string
|
||||
}
|
||||
|
||||
func MapInfoToPrintInfoFromIface(ctrls []reportsummary.IControlSummary) []InfoStars {
|
||||
infoToPrintInfo := []InfoStars{}
|
||||
infoToPrintInfoMap := map[string]interface{}{}
|
||||
starCount := "*"
|
||||
for _, ctrl := range ctrls {
|
||||
if ctrl.GetStatus().IsSkipped() && ctrl.GetStatus().Info() != "" {
|
||||
if _, ok := infoToPrintInfoMap[ctrl.GetStatus().Info()]; !ok {
|
||||
infoToPrintInfo = append(infoToPrintInfo, InfoStars{
|
||||
Info: ctrl.GetStatus().Info(),
|
||||
Stars: starCount,
|
||||
})
|
||||
starCount += "*"
|
||||
infoToPrintInfoMap[ctrl.GetStatus().Info()] = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return infoToPrintInfo
|
||||
}
|
||||
|
||||
func MapInfoToPrintInfo(controls reportsummary.ControlSummaries) []InfoStars {
|
||||
infoToPrintInfo := []InfoStars{}
|
||||
infoToPrintInfoMap := map[string]interface{}{}
|
||||
|
||||
@@ -27,15 +27,6 @@ var (
|
||||
complianceFrameworks = []string{"nsa", "mitre"}
|
||||
)
|
||||
|
||||
func printComplianceScore(writer *os.File, frameworks []reportsummary.IFrameworkSummary) {
|
||||
cautils.InfoTextDisplay(writer, "Compliance Score:\n")
|
||||
for _, fw := range frameworks {
|
||||
cautils.SimpleDisplay(writer, "* %s: %.2f%%\n", fw.GetName(), fw.GetComplianceScore())
|
||||
}
|
||||
|
||||
cautils.InfoTextDisplay(writer, "\n")
|
||||
}
|
||||
|
||||
func filterComplianceFrameworks(frameworks []reportsummary.IFrameworkSummary) []reportsummary.IFrameworkSummary {
|
||||
complianceFws := []reportsummary.IFrameworkSummary{}
|
||||
for _, fw := range frameworks {
|
||||
@@ -57,16 +48,8 @@ func getWorkloadPrefixForCmd(namespace, kind, name string) string {
|
||||
return fmt.Sprintf("namespace: %s, name: %s, kind: %s", namespace, name, kind)
|
||||
}
|
||||
|
||||
func printNextSteps(writer *os.File, nextSteps []string) {
|
||||
cautils.InfoTextDisplay(writer, "Follow-up steps:\n")
|
||||
for _, ns := range nextSteps {
|
||||
cautils.SimpleDisplay(writer, "- "+ns+"\n")
|
||||
}
|
||||
cautils.SimpleDisplay(writer, "\n")
|
||||
}
|
||||
|
||||
func getTopWorkloadsTitle(topWLsLen int) string {
|
||||
if topWLsLen > 2 {
|
||||
if topWLsLen > 1 {
|
||||
return "Your most risky workloads:\n"
|
||||
}
|
||||
if topWLsLen > 0 {
|
||||
@@ -75,6 +58,80 @@ func getTopWorkloadsTitle(topWLsLen int) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// getSeverityToSummaryMap returns a map of severity to summary, if shouldMerge is true, it will merge Low, Negligible and Unknown to Other
|
||||
func getSeverityToSummaryMap(summary imageprinter.ImageScanSummary, shouldMerge bool) map[string]*imageprinter.SeveritySummary {
|
||||
tempMap := map[string]*imageprinter.SeveritySummary{}
|
||||
for severity, severitySummary := range summary.MapsSeverityToSummary {
|
||||
if shouldMerge {
|
||||
if severity == "Low" || severity == "Negligible" || severity == "Unknown" {
|
||||
severity = "Other"
|
||||
}
|
||||
}
|
||||
if _, ok := tempMap[severity]; !ok {
|
||||
tempMap[severity] = &imageprinter.SeveritySummary{}
|
||||
}
|
||||
tempMap[severity].NumberOfCVEs += severitySummary.NumberOfCVEs
|
||||
tempMap[severity].NumberOfFixableCVEs += severitySummary.NumberOfFixableCVEs
|
||||
}
|
||||
return tempMap
|
||||
}
|
||||
|
||||
// filterCVEsBySeverities returns a list of CVEs only with the severities that are in the severities list
|
||||
func filterCVEsBySeverities(cves []imageprinter.CVE, severities []string) []imageprinter.CVE {
|
||||
var filteredCVEs []imageprinter.CVE
|
||||
|
||||
for _, cve := range cves {
|
||||
for _, severity := range severities {
|
||||
if cve.Severity == severity {
|
||||
filteredCVEs = append(filteredCVEs, cve)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return filteredCVEs
|
||||
}
|
||||
|
||||
// sortTopVulnerablePackages sorts the top vulnerable packages by score. It return a map of packages to their score and version
|
||||
func sortTopVulnerablePackages(pkgScores map[string]*imageprinter.PackageScore) map[string]*imageprinter.PackageScore {
|
||||
var ss []string
|
||||
for k := range pkgScores {
|
||||
ss = append(ss, k)
|
||||
}
|
||||
|
||||
sort.Slice(ss, func(i, j int) bool {
|
||||
return pkgScores[ss[i]].Score > pkgScores[ss[j]].Score
|
||||
})
|
||||
|
||||
var sortedMap = make(map[string]*imageprinter.PackageScore)
|
||||
|
||||
for i := 0; i < len(ss) && i < TopPackagesNumber; i++ {
|
||||
sortedMap[ss[i]] = &imageprinter.PackageScore{
|
||||
Score: pkgScores[ss[i]].Score,
|
||||
Version: pkgScores[ss[i]].Version,
|
||||
}
|
||||
}
|
||||
|
||||
return sortedMap
|
||||
}
|
||||
|
||||
// / PRINTERS ///
|
||||
func printTopVulnerabilities(writer *os.File, summary imageprinter.ImageScanSummary) {
|
||||
if len(summary.PackageScores) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
cautils.InfoTextDisplay(writer, "\nMost vulnerable components:\n")
|
||||
|
||||
topVulnerablePackages := sortTopVulnerablePackages(summary.PackageScores)
|
||||
for k, v := range topVulnerablePackages {
|
||||
cautils.SimpleDisplay(writer, " * %s (%s)\n", k, v.Version)
|
||||
}
|
||||
|
||||
cautils.SimpleDisplay(writer, "\n")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func printImageScanningSummary(writer *os.File, summary imageprinter.ImageScanSummary, verboseMode bool) {
|
||||
mapSeverityTSummary := getSeverityToSummaryMap(summary, !verboseMode)
|
||||
|
||||
@@ -98,71 +155,21 @@ func printImageScanningSummary(writer *os.File, summary imageprinter.ImageScanSu
|
||||
}
|
||||
}
|
||||
|
||||
func getSeverityToSummaryMap(summary imageprinter.ImageScanSummary, shouldMerge bool) map[string]*imageprinter.SeveritySummary {
|
||||
tempMap := map[string]*imageprinter.SeveritySummary{}
|
||||
for severity, severitySummary := range summary.MapsSeverityToSummary {
|
||||
if shouldMerge && severity == "Low" || severity == "Negligible" || severity == "Unknown" {
|
||||
severity = "Other"
|
||||
}
|
||||
if _, ok := tempMap[severity]; !ok {
|
||||
tempMap[severity] = &imageprinter.SeveritySummary{}
|
||||
}
|
||||
tempMap[severity].NumberOfCVEs += severitySummary.NumberOfCVEs
|
||||
tempMap[severity].NumberOfFixableCVEs += severitySummary.NumberOfFixableCVEs
|
||||
func printNextSteps(writer *os.File, nextSteps []string) {
|
||||
cautils.InfoTextDisplay(writer, "Follow-up steps:\n")
|
||||
for _, ns := range nextSteps {
|
||||
cautils.SimpleDisplay(writer, "- "+ns+"\n")
|
||||
}
|
||||
return tempMap
|
||||
}
|
||||
|
||||
func filterCVEsBySeverities(cves []imageprinter.CVE, severities []string) []imageprinter.CVE {
|
||||
var filteredCves []imageprinter.CVE
|
||||
|
||||
for _, cve := range cves {
|
||||
for _, severity := range severities {
|
||||
if cve.Severity == severity {
|
||||
filteredCves = append(filteredCves, cve)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return filteredCves
|
||||
}
|
||||
|
||||
func sortTopVulnerableWorkloads(pkgScores map[string]*imageprinter.Package) map[string]*imageprinter.Package {
|
||||
var ss []string
|
||||
for k := range pkgScores {
|
||||
ss = append(ss, k)
|
||||
}
|
||||
|
||||
sort.Slice(ss, func(i, j int) bool {
|
||||
return pkgScores[ss[i]].Score > pkgScores[ss[j]].Score
|
||||
})
|
||||
|
||||
var sortedMap = make(map[string]*imageprinter.Package)
|
||||
|
||||
for i := 0; i < len(ss) && i < TopPackagesNumber; i++ {
|
||||
|
||||
sortedMap[ss[i]] = &imageprinter.Package{
|
||||
Score: pkgScores[ss[i]].Score,
|
||||
Version: pkgScores[ss[i]].Version,
|
||||
}
|
||||
}
|
||||
|
||||
return sortedMap
|
||||
}
|
||||
|
||||
func printTopVulnerabilities(writer *os.File, summary imageprinter.ImageScanSummary) {
|
||||
if len(summary.PackageScores) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
cautils.InfoTextDisplay(writer, "\nMost vulnerable components:\n")
|
||||
|
||||
topVulnerableImages := sortTopVulnerableWorkloads(summary.PackageScores)
|
||||
for k, v := range topVulnerableImages {
|
||||
cautils.SimpleDisplay(writer, " * %s (%s)\n", k, v.Version)
|
||||
}
|
||||
|
||||
cautils.SimpleDisplay(writer, "\n")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func printComplianceScore(writer *os.File, frameworks []reportsummary.IFrameworkSummary) {
|
||||
cautils.InfoTextDisplay(writer, "Compliance Score:\n")
|
||||
for _, fw := range frameworks {
|
||||
cautils.SimpleDisplay(writer, "* %s: %.2f%%\n", fw.GetName(), fw.GetComplianceScore())
|
||||
}
|
||||
|
||||
cautils.SimpleDisplay(writer, "View full compliance report by running: $ kubescape scan framework nsa,mitre\n")
|
||||
|
||||
cautils.InfoTextDisplay(writer, "\n")
|
||||
}
|
||||
|
||||
@@ -22,12 +22,12 @@ func NewWorkloadPrinter(writer *os.File) *WorkloadPrinter {
|
||||
|
||||
var _ MainPrinter = &WorkloadPrinter{}
|
||||
|
||||
func (wp *WorkloadPrinter) PrintImageScanning(*imageprinter.ImageScanSummary) {
|
||||
func (wp *WorkloadPrinter) PrintImageScanning(summary *imageprinter.ImageScanSummary) {
|
||||
}
|
||||
|
||||
func (wp *WorkloadPrinter) PrintNextSteps() {}
|
||||
|
||||
func (wp *WorkloadPrinter) PrintConfigurationsScanning(summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) {
|
||||
wp.categoriesTablePrinter.PrintCategoriesTable(wp.writer, summaryDetails, sortedControlIDs)
|
||||
wp.categoriesTablePrinter.PrintCategoriesTables(wp.writer, summaryDetails, sortedControlIDs)
|
||||
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ func (pp *PrometheusPrinter) generatePrometheusFormat(
|
||||
func (pp *PrometheusPrinter) PrintImageScan(context.Context, *models.PresenterConfig) {
|
||||
}
|
||||
|
||||
func (pp *PrometheusPrinter) ActionPrint(ctx context.Context, opaSessionObj *cautils.OPASessionObj, imageScanData *models.PresenterConfig) {
|
||||
func (pp *PrometheusPrinter) ActionPrint(ctx context.Context, opaSessionObj *cautils.OPASessionObj, imageScanData []cautils.ImageScanData) {
|
||||
|
||||
metrics := pp.generatePrometheusFormat(opaSessionObj.AllResources, opaSessionObj.ResourcesResult, &opaSessionObj.Report.SummaryDetails)
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ func (sp *SARIFPrinter) PrintNextSteps() {
|
||||
|
||||
}
|
||||
|
||||
func (sp *SARIFPrinter) ActionPrint(ctx context.Context, opaSessionObj *cautils.OPASessionObj, imageScanData *models.PresenterConfig) {
|
||||
func (sp *SARIFPrinter) ActionPrint(ctx context.Context, opaSessionObj *cautils.OPASessionObj, imageScanData []cautils.ImageScanData) {
|
||||
report, err := sarif.New(sarif.Version210)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
@@ -21,7 +21,7 @@ func (silentPrinter *SilentPrinter) PrintNextSteps() {
|
||||
func (silentPrinter *SilentPrinter) PrintImageScan(context.Context, *models.PresenterConfig) {
|
||||
}
|
||||
|
||||
func (silentPrinter *SilentPrinter) ActionPrint(ctx context.Context, opaSessionObj *cautils.OPASessionObj, imageScanData *models.PresenterConfig) {
|
||||
func (silentPrinter *SilentPrinter) ActionPrint(ctx context.Context, opaSessionObj *cautils.OPASessionObj, imageScanData []cautils.ImageScanData) {
|
||||
}
|
||||
|
||||
func (silentPrinter *SilentPrinter) SetWriter(ctx context.Context, outputFile string) {
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package printer
|
||||
|
||||
import (
|
||||
v5 "github.com/anchore/grype/grype/db/v5"
|
||||
"github.com/anchore/grype/grype/presenter/models"
|
||||
"github.com/kubescape/k8s-interface/workloadinterface"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/imageprinter"
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils"
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
"github.com/kubescape/opa-utils/reporthandling/results/v1/prioritization"
|
||||
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
|
||||
@@ -82,3 +86,72 @@ func finalizeResources(results []resourcesresults.Result, allResources map[strin
|
||||
}
|
||||
return resources
|
||||
}
|
||||
|
||||
func insertSeveritiesSummariesIntoMap(mapSeverityToSummary map[string]*imageprinter.SeveritySummary, imageScanSummary imageprinter.ImageScanSummary) {
|
||||
for k, v := range mapSeverityToSummary {
|
||||
severitySummary, ok := imageScanSummary.MapsSeverityToSummary[k]
|
||||
if !ok {
|
||||
imageScanSummary.MapsSeverityToSummary[k] = v
|
||||
continue
|
||||
}
|
||||
severitySummary.NumberOfCVEs = severitySummary.NumberOfCVEs + v.NumberOfCVEs
|
||||
severitySummary.NumberOfFixableCVEs = severitySummary.NumberOfFixableCVEs + v.NumberOfFixableCVEs
|
||||
imageScanSummary.MapsSeverityToSummary[k] = severitySummary
|
||||
}
|
||||
}
|
||||
|
||||
func insertPackageScoresIntoMap(mapPackageNameToScore map[string]*imageprinter.PackageScore, imageScanSummary imageprinter.ImageScanSummary) {
|
||||
for k, v := range mapPackageNameToScore {
|
||||
pkgScore, ok := imageScanSummary.PackageScores[k]
|
||||
if !ok {
|
||||
imageScanSummary.PackageScores[k] = v
|
||||
continue
|
||||
}
|
||||
pkgScore.Score = pkgScore.Score + v.Score
|
||||
imageScanSummary.PackageScores[k] = pkgScore
|
||||
}
|
||||
}
|
||||
|
||||
func extractSeverityToSummaryMap(cves []imageprinter.CVE) map[string]*imageprinter.SeveritySummary {
|
||||
mapSeverityToSummary := map[string]*imageprinter.SeveritySummary{}
|
||||
for _, cve := range cves {
|
||||
if _, ok := mapSeverityToSummary[cve.Severity]; !ok {
|
||||
mapSeverityToSummary[cve.Severity] = &imageprinter.SeveritySummary{}
|
||||
}
|
||||
mapSeverityToSummary[cve.Severity].NumberOfCVEs = mapSeverityToSummary[cve.Severity].NumberOfCVEs + 1
|
||||
if cve.FixedState == string(v5.FixedState) {
|
||||
mapSeverityToSummary[cve.Severity].NumberOfFixableCVEs = mapSeverityToSummary[cve.Severity].NumberOfFixableCVEs + 1
|
||||
}
|
||||
}
|
||||
return mapSeverityToSummary
|
||||
}
|
||||
|
||||
func extractPkgNameToScore(doc models.Document) map[string]*imageprinter.PackageScore {
|
||||
mapPackageNameToScore := make(map[string]*imageprinter.PackageScore, 0)
|
||||
for _, cve := range doc.Matches {
|
||||
if _, ok := mapPackageNameToScore[cve.Artifact.Name]; !ok {
|
||||
mapPackageNameToScore[cve.Artifact.Name] = &imageprinter.PackageScore{
|
||||
Score: 0,
|
||||
}
|
||||
}
|
||||
mapPackageNameToScore[cve.Artifact.Name].Score = mapPackageNameToScore[cve.Artifact.Name].Score + utils.ImageSeverityToInt(cve.Vulnerability.Severity)
|
||||
mapPackageNameToScore[cve.Artifact.Name].Version = cve.Artifact.Version
|
||||
}
|
||||
return mapPackageNameToScore
|
||||
}
|
||||
|
||||
func extractCVEs(doc models.Document) []imageprinter.CVE {
|
||||
cves := []imageprinter.CVE{}
|
||||
for _, match := range doc.Matches {
|
||||
cve := imageprinter.CVE{
|
||||
ID: match.Vulnerability.ID,
|
||||
Severity: match.Vulnerability.Severity,
|
||||
Package: match.Artifact.Name,
|
||||
Version: match.Artifact.Version,
|
||||
FixVersions: match.Vulnerability.Fix.Versions,
|
||||
FixedState: match.Vulnerability.Fix.State,
|
||||
}
|
||||
cves = append(cves, cve)
|
||||
}
|
||||
return cves
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ type ResultsHandler struct {
|
||||
UiPrinter printer.IPrinter
|
||||
ScanData *cautils.OPASessionObj
|
||||
PrinterObjs []printer.IPrinter
|
||||
ImageScanData *models.PresenterConfig
|
||||
ImageScanData []cautils.ImageScanData
|
||||
}
|
||||
|
||||
func NewResultsHandler(reporterObj reporter.IReport, printerObjs []printer.IPrinter, uiPrinter printer.IPrinter, imageScanData *models.PresenterConfig) *ResultsHandler {
|
||||
@@ -29,7 +29,7 @@ func NewResultsHandler(reporterObj reporter.IReport, printerObjs []printer.IPrin
|
||||
ReporterObj: reporterObj,
|
||||
PrinterObjs: printerObjs,
|
||||
UiPrinter: uiPrinter,
|
||||
ImageScanData: imageScanData,
|
||||
ImageScanData: make([]cautils.ImageScanData, 0),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
2
go.mod
2
go.mod
@@ -2,6 +2,8 @@ module github.com/kubescape/kubescape/v2
|
||||
|
||||
go 1.20
|
||||
|
||||
replace github.com/kubescape/opa-utils => github.com/kubescape/opa-utils v0.0.255-0.20230720064723-b84bab720db2
|
||||
|
||||
require (
|
||||
cloud.google.com/go/containeranalysis v0.9.0
|
||||
github.com/anchore/grype v0.64.2
|
||||
|
||||
2
go.sum
2
go.sum
@@ -1519,6 +1519,8 @@ github.com/kubescape/go-logger v0.0.11 h1:oucpq2S7+DT7O+UclG5IrmHado/tj6+IkYf9cz
|
||||
github.com/kubescape/go-logger v0.0.11/go.mod h1:yGiKBJ2lhq/kxzY/MVYDREL9fLV3RGD6gv+UFjslaew=
|
||||
github.com/kubescape/k8s-interface v0.0.116 h1:Sn76gsMLAArc5kbHZVoRMS6QlM4mOz9Dolpym9BOul8=
|
||||
github.com/kubescape/k8s-interface v0.0.116/go.mod h1:ENpA9SkkS6E3PIT+AaMu/JGkuyE04aUamY+a7WLqsJQ=
|
||||
github.com/kubescape/opa-utils v0.0.255-0.20230720064723-b84bab720db2 h1:hr6Gqb7MTb/pLkwpD9fnf/qw2l/khh2FXc2uND1xx9I=
|
||||
github.com/kubescape/opa-utils v0.0.255-0.20230720064723-b84bab720db2/go.mod h1:SkNqbhUGipSYVE+oAUaHko6aggp8XVVbDChoNg48lao=
|
||||
github.com/kubescape/rbac-utils v0.0.20 h1:1MMxsCsCZ3ntDi8f9ZYYcY+K7bv50bDW5ZvnGnhMhJw=
|
||||
github.com/kubescape/rbac-utils v0.0.20/go.mod h1:t57AhSrjuNGQ+mpZWQM/hBzrCOeKBDHegFoVo4tbikQ=
|
||||
github.com/kubescape/regolibrary v1.0.286-rc.0 h1:OzhtQEx1npAxTbgkbEpMLZvPWg6sh2CmCgQLs0j6pQ4=
|
||||
|
||||
Reference in New Issue
Block a user