Pretty up the pretty-printer. (#1451)

* Pretty up the pretty-printer.

Signed-off-by: Craig Box <craigb@armosec.io>

* add some text fixes for the Operator also

Signed-off-by: Craig Box <craigb@armosec.io>

* fix another verb

Signed-off-by: Craig Box <craigb@armosec.io>

* fixed unit tests

Signed-off-by: David Wertenteil <dwertent@armosec.io>

* fixed test

Signed-off-by: David Wertenteil <dwertent@armosec.io>

---------

Signed-off-by: Craig Box <craigb@armosec.io>
Signed-off-by: David Wertenteil <dwertent@armosec.io>
Co-authored-by: David Wertenteil <dwertent@armosec.io>
This commit is contained in:
Craig Box
2023-10-31 18:07:43 +02:00
committed by GitHub
co-authored by David Wertenteil
parent 3b2feca0dd
commit c9ccef90f3
34 changed files with 201 additions and 140 deletions
+4 -4
View File
@@ -21,7 +21,7 @@ var operatorScanConfigExamples = fmt.Sprintf(`
func getOperatorScanConfigCmd(ks meta.IKubescape, operatorInfo cautils.OperatorInfo) *cobra.Command {
configCmd := &cobra.Command{
Use: "configurations",
Short: "Trigger configuration scanning from the Kubescape-Operator microservice",
Short: "Trigger configuration scanning from the Kubescape Operator microservice",
Long: ``,
Example: operatorScanConfigExamples,
Args: func(cmd *cobra.Command, args []string) error {
@@ -33,13 +33,13 @@ func getOperatorScanConfigCmd(ks meta.IKubescape, operatorInfo cautils.OperatorI
if err != nil {
return err
}
logger.L().Start("Kubescape-Operator Triggering for configuration scanning")
logger.L().Start("Kubescape Operator Triggering for configuration scanning")
_, err = operatorAdapter.OperatorScan()
if err != nil {
logger.L().StopError("Failed to triggering Kubescape-Operator for configuration scanning", helpers.Error(err))
logger.L().StopError("Failed to triggering Kubescape Operator for configuration scanning", helpers.Error(err))
return err
}
logger.L().StopSuccess("Triggered Kubescape-Operator for configuration scanning")
logger.L().StopSuccess("Triggered Kubescape Operator for configuration scanning")
return nil
},
}
+1 -1
View File
@@ -29,7 +29,7 @@ func GetOperatorCmd(ks meta.IKubescape) *cobra.Command {
operatorCmd := &cobra.Command{
Use: "operator",
Short: "The operator is used to communicate with the Kubescape-Operator within the cluster components.",
Short: "The operator is used to communicate with the Kubescape Operator within the cluster components.",
Long: ``,
Example: operatorExamples,
Args: func(cmd *cobra.Command, args []string) error {
+3 -3
View File
@@ -34,13 +34,13 @@ func getOperatorScanVulnerabilitiesCmd(ks meta.IKubescape, operatorInfo cautils.
if err != nil {
return err
}
logger.L().Start("Triggering the Kubescape-Operator for vulnerability scanning")
logger.L().Start("Triggering the Kubescape Operator for vulnerability scanning")
_, err = operatorAdapter.OperatorScan()
if err != nil {
logger.L().StopError("Failed to trigger the Kubescape-Operator for vulnerability scanning", helpers.Error(err))
logger.L().StopError("Failed to trigger the Kubescape Operator for vulnerability scanning", helpers.Error(err))
return err
}
logger.L().StopSuccess("Triggered Kubescape-Operator for vulnerability scanning. View the scanning results once they are ready using the following command: \"kubectl get vulnerabilitysummaries\"")
logger.L().StopSuccess("Triggered Kubescape Operator for vulnerability scanning. View the scanning results once they are ready using the following command: \"kubectl get vulnerabilitysummaries\"")
return err
},
}
-3
View File
@@ -126,9 +126,6 @@ func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comm
logger.L().Fatal(err.Error())
}
if !scanInfo.VerboseMode && scanInfo.ScanType == cautils.ScanTypeFramework {
logger.L().Info("Run with '--verbose'/'-v' flag for detailed resources view\n")
}
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)))
}
+3 -3
View File
@@ -14,7 +14,7 @@ import (
)
const (
installationLink string = "https://github.com/kubescape/kubescape/blob/master/docs/installation.md"
installationLink string = "https://kubescape.io/docs/install-cli/"
)
var updateCmdExamples = fmt.Sprintf(`
@@ -32,9 +32,9 @@ func GetUpdateCmd() *cobra.Command {
//Checking the user's version of kubescape to the latest release
if cautils.BuildNumber == cautils.LatestReleaseVersion {
//your version == latest version
logger.L().Info(("Nothing to update, you are running the latest version"), helpers.String("Version", cautils.BuildNumber))
logger.L().Info(("Nothing to update: you are running the latest version"), helpers.String("Version", cautils.BuildNumber))
} else {
fmt.Printf("Please refer to our installation docs in the following link: %s\n", installationLink)
fmt.Printf("Please refer to our installation documentation: %s\n", installationLink)
}
return nil
},
+16 -1
View File
@@ -4,6 +4,7 @@ import (
"fmt"
"io"
"os"
"strings"
"time"
spinnerpkg "github.com/briandowns/spinner"
@@ -27,7 +28,7 @@ func FailureTextDisplay(w io.Writer, format string, a ...interface{}) {
}
func InfoDisplay(w io.Writer, format string, a ...interface{}) {
fmt.Fprintf(w, gchalk.WithCyan().Bold(format), a...)
fmt.Fprintf(w, gchalk.WithBrightWhite().Bold(format), a...)
}
func InfoTextDisplay(w io.Writer, format string, a ...interface{}) {
@@ -50,6 +51,20 @@ func BoldDisplay(w io.Writer, format string, a ...interface{}) {
fmt.Fprintf(w, gchalk.Bold(format), a...)
}
func LineDisplay(w io.Writer, format string, a ...interface{}) {
fmt.Fprintf(w, gchalk.WithAnsi256(238).Bold(format), a...)
}
func SectionHeadingDisplay(w io.Writer, format string, a ...interface{}) {
fmt.Fprintf(w, "\n"+
gchalk.WithBrightWhite().Bold(format)+
gchalk.WithAnsi256(238).Bold(fmt.Sprintf("\n%s\n\n", strings.Repeat("─", len(format)))), a...)
}
func StarDisplay(w io.Writer, format string, a ...interface{}) {
fmt.Fprintf(w, gchalk.WithAnsi256(238).Bold("* ")+gchalk.White(format), a...)
}
var spinner *spinnerpkg.Spinner
func StartSpinner() {
+1 -1
View File
@@ -31,7 +31,7 @@ type IVersionCheckHandler interface {
func NewIVersionCheckHandler(ctx context.Context) IVersionCheckHandler {
if BuildNumber == "" {
logger.L().Ctx(ctx).Warning("Unknown build number, this might affect your scan results. Please make sure that you are running the latest version")
logger.L().Ctx(ctx).Warning("Unknown build number: this might affect your scan results. Please ensure that you are running the latest version.")
}
if v, ok := os.LookupEnv(CLIENT_ENV); ok && v != "" {
+1 -1
View File
@@ -36,7 +36,7 @@ func getOperatorPod(k8sClient *k8sinterface.KubernetesApi, ns string) (*v1.Pod,
return nil, err
}
if len(pods.Items) != 1 {
return nil, errors.New("Could not find the Kubescape-Operator chart, please validate that the Kubescape-Operator helm chart is installed and running -> https://github.com/kubescape/helm-charts")
return nil, errors.New("Could not find the Kubescape Operator chart, please validate that the Kubescape Operator helm chart is installed and running -> https://github.com/kubescape/helm-charts")
}
return &pods.Items[0], nil
+2 -2
View File
@@ -23,13 +23,13 @@ func Test_getOperatorPod(t *testing.T) {
name: "test error no operator exist",
createOperatorPod: false,
createAnotherOperatorPodWithSameLabel: false,
expectedError: fmt.Errorf("Could not find the Kubescape-Operator chart, please validate that the Kubescape-Operator helm chart is installed and running -> https://github.com/kubescape/helm-charts"),
expectedError: fmt.Errorf("Could not find the Kubescape Operator chart, please validate that the Kubescape Operator helm chart is installed and running -> https://github.com/kubescape/helm-charts"),
},
{
name: "test error several operators exist",
createOperatorPod: true,
createAnotherOperatorPodWithSameLabel: true,
expectedError: fmt.Errorf("Could not find the Kubescape-Operator chart, please validate that the Kubescape-Operator helm chart is installed and running -> https://github.com/kubescape/helm-charts"),
expectedError: fmt.Errorf("Could not find the Kubescape Operator chart, please validate that the Kubescape Operator helm chart is installed and running -> https://github.com/kubescape/helm-charts"),
},
{
name: "test no error",
+1 -1
View File
@@ -13,7 +13,7 @@ import (
)
func (ks *Kubescape) ScanImage(ctx context.Context, imgScanInfo *ksmetav1.ImageScanInfo, scanInfo *cautils.ScanInfo) (*models.PresenterConfig, error) {
logger.L().Start(fmt.Sprintf("Scanning image: %s", imgScanInfo.Image))
logger.L().Start(fmt.Sprintf("Scanning image %s...", imgScanInfo.Image))
dbCfg, _ := imagescan.NewDefaultDBConfig()
svc := imagescan.NewScanService(dbCfg)
+2 -2
View File
@@ -134,13 +134,13 @@ func prettyPrintControls(ctx context.Context, policies []string) {
controlRows := generateControlRows(policies)
short := utils.CheckShortTerminalWidth(controlRows, []string{"Control ID", "Control Name", "Docs", "Frameworks"})
short := utils.CheckShortTerminalWidth(controlRows, []string{"Control ID", "Control name", "Docs", "Frameworks"})
if short {
controlsTable.SetAutoWrapText(false)
controlsTable.SetHeader([]string{"Controls"})
controlRows = shortFormatControlRows(controlRows)
} else {
controlsTable.SetHeader([]string{"Control ID", "Control Name", "Docs", "Frameworks"})
controlsTable.SetHeader([]string{"Control ID", "Control name", "Docs", "Frameworks"})
}
var headerColors []tablewriter.Colors
for range controlRows[0] {
+1 -1
View File
@@ -71,7 +71,7 @@ func (ks *Kubescape) Patch(ctx context.Context, patchInfo *ksmetav1.PatchInfo, s
// ===================== Re-scan the image =====================
logger.L().Start(fmt.Sprintf("Re-Scanning image: %s", patchedImageName))
logger.L().Start(fmt.Sprintf("Re-scanning image: %s", patchedImageName))
scanResultsPatched, err := svc.Scan(ctx, patchedImageName, creds)
if err != nil {
+2 -2
View File
@@ -119,7 +119,7 @@ func GetOutputPrinters(scanInfo *cautils.ScanInfo, ctx context.Context, clusterN
func (ks *Kubescape) Scan(ctx context.Context, scanInfo *cautils.ScanInfo) (*resultshandling.ResultsHandler, error) {
ctxInit, spanInit := otel.Tracer("").Start(ctx, "initialization")
logger.L().Start("Kubescape scanner initializing")
logger.L().Start("Kubescape scanner initializing...")
// ===================== Initialization =====================
scanInfo.Init(ctxInit) // initialize scan info
@@ -246,7 +246,7 @@ func scanImages(scanType cautils.ScanTypes, scanData *cautils.OPASessionObj, ctx
if err := scanSingleImage(ctx, img, svc, resultsHandling); err != nil {
logger.L().StopError("failed to scan", helpers.String("image", img), helpers.Error(err))
}
logger.L().StopSuccess("Scanned successfully", helpers.String("image", img))
logger.L().StopSuccess("Scan successful: ", helpers.String("image", img))
}
}
+3 -3
View File
@@ -70,7 +70,7 @@ func (policyHandler *PolicyHandler) getPolicies(ctx context.Context, policyIdent
ctx, span := otel.Tracer("").Start(ctx, "policyHandler.getPolicies")
defer span.End()
logger.L().Start("Loading policies")
logger.L().Start("Loading policies...")
// get policies
policies, err = policyHandler.getScanPolicies(ctx, policyIdentifier)
@@ -82,7 +82,7 @@ func (policyHandler *PolicyHandler) getPolicies(ctx context.Context, policyIdent
}
logger.L().StopSuccess("Loaded policies")
logger.L().Start("Loading exceptions")
logger.L().Start("Loading exceptions...")
// get exceptions
if exceptions, err = policyHandler.getExceptions(); err != nil {
@@ -90,7 +90,7 @@ func (policyHandler *PolicyHandler) getPolicies(ctx context.Context, policyIdent
}
logger.L().StopSuccess("Loaded exceptions")
logger.L().Start("Loading account configurations")
logger.L().Start("Loading account configurations...")
// get account configuration
if controlInputs, err = policyHandler.getControlInputs(); err != nil {
+1 -1
View File
@@ -34,7 +34,7 @@ func (fileHandler *FileResourceHandler) GetResources(ctx context.Context, sessio
return nil, nil, nil, nil, fmt.Errorf("missing input")
}
logger.L().Start("Accessing local objects")
logger.L().Start("Accessing local objects...")
// load resources from all input paths
mappedResources := map[string][]workloadinterface.IMetadata{}
+4 -4
View File
@@ -61,7 +61,7 @@ func NewK8sResourceHandler(k8s *k8sinterface.KubernetesApi, hostSensorHandler ho
}
func (k8sHandler *K8sResourceHandler) GetResources(ctx context.Context, sessionObj *cautils.OPASessionObj, progressListener opaprocessor.IJobProgressNotificationClient, scanInfo *cautils.ScanInfo) (cautils.K8SResources, map[string]workloadinterface.IMetadata, cautils.ExternalResources, map[string]bool, error) {
logger.L().Start("Accessing Kubernetes objects")
logger.L().Start("Accessing Kubernetes objects...")
var err error
globalFieldSelectors := getFieldSelectorFromScanInfo(scanInfo)
@@ -130,7 +130,7 @@ func (k8sHandler *K8sResourceHandler) GetResources(ctx context.Context, sessionO
cautils.StopSpinner()
logger.L().Success("Requested Host scanner data")
} else {
cautils.SetInfoMapForResources("This control requires the host-scanner capability. To activate the host scanner capability, proceed with the installation of the kubescape operator chart found here: https://github.com/kubescape/helm-charts/tree/main/charts/kubescape-operator", hostResources, sessionObj.InfoMap)
cautils.SetInfoMapForResources("This control requires the Kubescape operator installed. To install it, go to\n https://kubescape.io/docs/install-operator/.", hostResources, sessionObj.InfoMap)
}
}
@@ -212,7 +212,7 @@ func (k8sHandler *K8sResourceHandler) collectCloudResources(ctx context.Context,
return fmt.Errorf("failed to get cloud provider, cluster: %s", k8sHandler.clusterName)
}
logger.L().Start("Downloading cloud resources")
logger.L().Start("Downloading cloud resources...")
if sessionObj.Metadata != nil && sessionObj.Metadata.ContextMetadata.ClusterContextMetadata != nil {
sessionObj.Metadata.ContextMetadata.ClusterContextMetadata.CloudProvider = k8sHandler.cloudProvider
@@ -423,7 +423,7 @@ func (k8sHandler *K8sResourceHandler) collectRbacResources(allResources map[stri
return nil
}
logger.L().Start("Collecting RBAC resources")
logger.L().Start("Collecting RBAC resources...")
allRbacResources, err := k8sHandler.rbacObjectsAPI.ListAllResources()
if err != nil {
return err
@@ -144,11 +144,11 @@ func getControlTableHeaders(short bool) []string {
headers[0] = "Controls"
} else {
headers = make([]string, _rowLen)
headers[columnName] = "Control Name"
headers[columnCounterFailed] = "Failed Resources"
headers[columnCounterAll] = "All Resources"
headers[columnName] = "Control name"
headers[columnCounterFailed] = "Failed resources"
headers[columnCounterAll] = "All resources"
headers[columnSeverity] = "Severity"
headers[columnComplianceScore] = "% Compliane-Score"
headers[columnComplianceScore] = "Compliance score"
}
return headers
}
@@ -10,6 +10,7 @@ import (
"github.com/anchore/grype/grype/presenter/models"
"github.com/enescakir/emoji"
"github.com/jwalton/gchalk"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/k8s-interface/workloadinterface"
@@ -20,6 +21,7 @@ import (
"github.com/kubescape/opa-utils/objectsenvelopes"
"github.com/kubescape/opa-utils/reporthandling/apis"
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
"github.com/olekukonko/tablewriter"
"k8s.io/utils/strings/slices"
)
@@ -118,8 +120,11 @@ func (pp *PrettyPrinter) PrintImageScan(imageScanData []cautils.ImageScanData) {
func (pp *PrettyPrinter) ActionPrint(_ context.Context, opaSessionObj *cautils.OPASessionObj, imageScanData []cautils.ImageScanData) {
if opaSessionObj != nil {
// TODO line is currently printed on framework scan only
if isPrintSeparatorType(pp.scanType) {
fmt.Fprintf(pp.writer, "\n"+getSeparator("^")+"\n")
fmt.Fprintf(pp.writer, "\n"+
gchalk.WithAnsi256(238).Bold(fmt.Sprintf("%s\n", strings.Repeat("─", 50)))+
"\n")
} else {
fmt.Fprintf(pp.writer, "\n")
}
@@ -166,13 +171,26 @@ func (pp *PrettyPrinter) printHeader(opaSessionObj *cautils.OPASessionObj) {
cautils.InfoDisplay(pp.writer, fmt.Sprintf("\nKubescape security posture overview for cluster: %s\n\n", pp.clusterName))
cautils.SimpleDisplay(pp.writer, "In this overview, Kubescape shows you a summary of your cluster security posture, including the number of users who can perform administrative actions. For each result greater than 0, you should evaluate its need, and then define an exception to allow it. This baseline can be used to detect drift in future.\n\n")
} else if pp.scanType == cautils.ScanTypeWorkload {
cautils.InfoDisplay(pp.writer, "Workload security posture overview for:\n")
ns := opaSessionObj.SingleResourceScan.GetNamespace()
if ns == "" {
cautils.InfoDisplay(pp.writer, "Workload - Kind: %s, Name: %s\n\n", opaSessionObj.SingleResourceScan.GetKind(), opaSessionObj.SingleResourceScan.GetName())
} else {
cautils.InfoDisplay(pp.writer, "Workload - Namespace: %s, Kind: %s, Name: %s\n\n", opaSessionObj.SingleResourceScan.GetNamespace(), opaSessionObj.SingleResourceScan.GetKind(), opaSessionObj.SingleResourceScan.GetName())
rows := [][]string{}
if ns != "" {
rows = append(rows, []string{"Namespace", gchalk.WithBrightWhite().Bold(opaSessionObj.SingleResourceScan.GetNamespace())})
}
rows = append(rows, []string{"Kind", gchalk.WithBrightWhite().Bold(opaSessionObj.SingleResourceScan.GetKind())})
rows = append(rows, []string{"Name", gchalk.WithBrightWhite().Bold(opaSessionObj.SingleResourceScan.GetName())})
table := tablewriter.NewWriter(pp.writer)
table.SetColumnAlignment([]int{tablewriter.ALIGN_RIGHT, tablewriter.ALIGN_LEFT})
table.SetUnicodeHVC(tablewriter.Regular, tablewriter.Regular, gchalk.Ansi256(238))
table.AppendBulk(rows)
table.Render()
cautils.SimpleDisplay(pp.writer, "\nIn this overview, Kubescape shows you a summary of the security posture of a workload, including key controls that apply to its configuration, and the vulnerability status of the container image.\n\n\n")
}
}
func (pp *PrettyPrinter) SetWriter(ctx context.Context, outputFile string) {
@@ -317,16 +335,15 @@ func generateRelatedObjectsStr(workload WorkloadSummary) string {
func frameworksScoresToString(frameworks []reportsummary.IFrameworkSummary) string {
if len(frameworks) == 1 {
if frameworks[0].GetName() != "" {
return fmt.Sprintf("FRAMEWORK %s\n", frameworks[0].GetName())
// cautils.InfoTextDisplay(prettyPrinter.writer, ))
return fmt.Sprintf("Framework scanned: %s\n", frameworks[0].GetName())
}
} else if len(frameworks) > 1 {
p := "FRAMEWORKS: "
p := "Frameworks scanned: "
i := 0
for ; i < len(frameworks)-1; i++ {
p += fmt.Sprintf("%s (compliance: %.2f), ", frameworks[i].GetName(), frameworks[i].GetComplianceScore())
p += fmt.Sprintf("%s (compliance score: %.2f%%), ", frameworks[i].GetName(), frameworks[i].GetComplianceScore())
}
p += fmt.Sprintf("%s (compliance: %.2f)\n", frameworks[i].GetName(), frameworks[i].GetComplianceScore())
p += fmt.Sprintf("%s (compliance score: %.2f%%)\n", frameworks[i].GetName(), frameworks[i].GetComplianceScore())
return p
}
return ""
@@ -3,7 +3,6 @@ package prettyprinter
import (
"fmt"
"os"
"strings"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter"
@@ -43,7 +42,7 @@ func (cp *ClusterPrinter) PrintConfigurationsScanning(summaryDetails *reportsumm
}
func (cp *ClusterPrinter) PrintNextSteps() {
printNextSteps(cp.writer, cp.getNextSteps(), false)
printNextSteps(cp.writer, cp.getNextSteps(), true)
}
func (cp *ClusterPrinter) getNextSteps() []string {
@@ -57,10 +56,7 @@ func (cp *ClusterPrinter) getNextSteps() []string {
func (cp *ClusterPrinter) printTopWorkloads(topWorkloadsByScore []reporthandling.IResource) {
txt := getTopWorkloadsTitle(len(topWorkloadsByScore))
cautils.InfoTextDisplay(cp.writer, txt)
cautils.SimpleDisplay(cp.writer, fmt.Sprintf("%s\n", strings.Repeat("─", len(txt))))
cautils.SectionHeadingDisplay(cp.writer, txt)
cautils.SimpleDisplay(cp.writer, highStakesWlsText)
for i, wl := range topWorkloadsByScore {
@@ -71,7 +67,7 @@ func (cp *ClusterPrinter) printTopWorkloads(topWorkloadsByScore []reporthandling
cautils.SimpleDisplay(cp.writer, fmt.Sprintf(" '%s'\n", getCallToActionString(cp.getWorkloadScanCommand(ns, kind, name))))
}
cautils.InfoTextDisplay(cp.writer, "\n")
cautils.SimpleDisplay(cp.writer, "\n")
}
func (cp *ClusterPrinter) getWorkloadScanCommand(namespace, kind, name string) string {
@@ -41,7 +41,7 @@ func (ip *ImagePrinter) PrintImageScanningTable(summary imageprinter.ImageScanSu
}
ip.imageTablePrinter.PrintImageScanningTable(ip.writer, summary)
cautils.InfoTextDisplay(ip.writer, "\n")
cautils.SimpleDisplay(ip.writer, "\n")
}
func (ip *ImagePrinter) PrintConfigurationsScanning(summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string, topWorkloadsByScore []reporthandling.IResource) {
@@ -3,7 +3,6 @@ package prettyprinter
import (
"fmt"
"os"
"strings"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter"
@@ -56,10 +55,7 @@ func (rp *RepoPrinter) getNextSteps() []string {
func (rp *RepoPrinter) printTopWorkloads(topWorkloadsByScore []reporthandling.IResource) {
txt := getTopWorkloadsTitle(len(topWorkloadsByScore))
cautils.InfoTextDisplay(rp.writer, txt)
cautils.SimpleDisplay(rp.writer, fmt.Sprintf("%s\n", strings.Repeat("─", len(txt))))
cautils.SectionHeadingDisplay(rp.writer, txt)
cautils.SimpleDisplay(rp.writer, highStakesWlsText)
for i, wl := range topWorkloadsByScore {
@@ -71,7 +67,7 @@ func (rp *RepoPrinter) printTopWorkloads(topWorkloadsByScore []reporthandling.IR
cautils.SimpleDisplay(rp.writer, fmt.Sprintf(" %s\n", getCallToActionString(rp.getWorkloadScanCommand(ns, kind, name, *wl.GetSource()))))
}
cautils.InfoTextDisplay(rp.writer, "\n")
cautils.SimpleDisplay(rp.writer, "\n")
}
func (rp *RepoPrinter) getWorkloadScanCommand(ns, kind, name string, source reporthandling.Source) string {
@@ -13,11 +13,11 @@ import (
const (
docsPrefix = "https://hub.armosec.io/docs"
scanControlPrefix = "$ kubescape scan control"
controlNameHeader = "Control Name"
controlNameHeader = "Control name"
statusHeader = ""
docsHeader = "Docs"
resourcesHeader = "Resources"
runHeader = "View Details"
runHeader = "View details"
)
// initializes the table headers and column alignments based on the category type
@@ -91,14 +91,15 @@ func getCategoryTableWriter(writer io.Writer, headers []string, columnAligments
table.SetUnicodeHVC(tablewriter.Regular, tablewriter.Regular, gchalk.Ansi256(238))
var headerColors []tablewriter.Colors
for range headers {
headerColors = append(headerColors, tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiYellowColor})
headerColors = append(headerColors, tablewriter.Colors{tablewriter.FgHiYellowColor})
}
table.SetHeaderColor(headerColors...)
return table
}
func renderSingleCategory(writer io.Writer, categoryName string, table *tablewriter.Table, rows [][]string, infoToPrintInfo []utils.InfoStars) {
cautils.InfoTextDisplay(writer, categoryName+"\n")
cautils.InfoDisplay(writer, categoryName+"\n")
table.ClearRows()
table.AppendBulk(rows)
@@ -21,13 +21,13 @@ func TestInitCategoryTableData(t *testing.T) {
{
name: "Test1",
categoryType: TypeCounting,
expectedHeaders: []string{"Control Name", "Resources", "View Details"},
expectedHeaders: []string{"Control name", "Resources", "View details"},
expectedAlignments: []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT},
},
{
name: "Test2",
categoryType: TypeStatus,
expectedHeaders: []string{"", "Control Name", "Docs"},
expectedHeaders: []string{"", "Control name", "Docs"},
expectedAlignments: []int{tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER},
},
}
@@ -3,6 +3,7 @@ package configurationprinter
import (
"fmt"
"io"
"strconv"
"strings"
"github.com/jwalton/gchalk"
@@ -30,11 +31,33 @@ func (fp *FrameworkPrinter) getVerboseMode() bool {
func (fp *FrameworkPrinter) PrintSummaryTable(writer io.Writer, summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) {
if summaryDetails.NumberOfControls().All() == 0 {
fmt.Fprintf(writer, "\nKubescape did not scan any of the resources, make sure you are scanning valid kubernetes manifests (Deployments, Pods, etc.)\n")
fmt.Fprintf(writer, "\nKubescape did not scan any resources. Make sure you are scanning valid manifests (Deployments, Pods, etc.)\n")
return
}
cautils.InfoTextDisplay(writer, "\n"+ControlCountersForSummary(summaryDetails.NumberOfControls())+"\n")
cautils.InfoTextDisplay(writer, renderSeverityCountersSummary(summaryDetails.GetResourcesSeverityCounters())+"\n\n")
// When scanning controls the framework list will be empty
cautils.SimpleDisplay(writer, utils.FrameworksScoresToString(summaryDetails.ListFrameworks())+"\n")
controlCountersTable := tablewriter.NewWriter(writer)
controlCountersTable.SetColumnAlignment([]int{tablewriter.ALIGN_RIGHT, tablewriter.ALIGN_LEFT})
controlCountersTable.SetUnicodeHVC(tablewriter.Regular, tablewriter.Regular, gchalk.Ansi256(238))
controlCountersTable.AppendBulk(ControlCountersForSummary(summaryDetails.NumberOfControls()))
controlCountersTable.Render()
cautils.SimpleDisplay(writer, "\nFailed resources by severity:\n\n")
severityCountersTable := tablewriter.NewWriter(writer)
severityCountersTable.SetColumnAlignment([]int{tablewriter.ALIGN_RIGHT, tablewriter.ALIGN_LEFT})
severityCountersTable.SetUnicodeHVC(tablewriter.Regular, tablewriter.Regular, gchalk.Ansi256(238))
severityCountersTable.AppendBulk(renderSeverityCountersSummary(summaryDetails.GetResourcesSeverityCounters()))
severityCountersTable.Render()
cautils.SimpleDisplay(writer, "\n")
if !fp.getVerboseMode() {
cautils.SimpleDisplay(writer, "Run with '--verbose'/'-v' to see control failures for each resource.\n\n")
}
summaryTable := tablewriter.NewWriter(writer)
@@ -82,9 +105,6 @@ func (fp *FrameworkPrinter) PrintSummaryTable(writer io.Writer, summaryDetails *
summaryTable.AppendBulk(dataRows)
summaryTable.Render()
// When scanning controls the framework list will be empty
cautils.InfoTextDisplay(writer, utils.FrameworksScoresToString(summaryDetails.ListFrameworks()))
utils.PrintInfo(writer, infoToPrintInfo)
}
@@ -100,14 +120,13 @@ func (fp *FrameworkPrinter) PrintCategoriesTables(writer io.Writer, summaryDetai
}
func renderSeverityCountersSummary(counters reportsummary.ISeverityCounters) string {
critical := counters.NumberOfCriticalSeverity()
high := counters.NumberOfHighSeverity()
medium := counters.NumberOfMediumSeverity()
low := counters.NumberOfLowSeverity()
func renderSeverityCountersSummary(counters reportsummary.ISeverityCounters) [][]string {
return fmt.Sprintf(
"Failed Resources by Severity: Critical — %d, High — %d, Medium — %d, Low — %d",
critical, high, medium, low,
)
rows := [][]string{}
rows = append(rows, []string{"Critical", utils.GetColorForVulnerabilitySeverity("Critical")(strconv.Itoa(counters.NumberOfCriticalSeverity()))})
rows = append(rows, []string{"High", utils.GetColorForVulnerabilitySeverity("High")(strconv.Itoa(counters.NumberOfHighSeverity()))})
rows = append(rows, []string{"Medium", utils.GetColorForVulnerabilitySeverity("Medium")(strconv.Itoa(counters.NumberOfMediumSeverity()))})
rows = append(rows, []string{"Low", utils.GetColorForVulnerabilitySeverity("Low")(strconv.Itoa(counters.NumberOfLowSeverity()))})
return rows
}
@@ -2,6 +2,7 @@ package configurationprinter
import (
"fmt"
"strconv"
"strings"
"github.com/kubescape/kubescape/v3/core/cautils"
@@ -20,8 +21,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 ControlCountersForSummary(counters reportsummary.ICounters) [][]string {
rows := [][]string{}
rows = append(rows, []string{"Controls", strconv.Itoa(counters.All())})
rows = append(rows, []string{"Passed", strconv.Itoa(counters.Passed())})
rows = append(rows, []string{"Failed", strconv.Itoa(counters.Failed())})
rows = append(rows, []string{"Action Required", strconv.Itoa(counters.Skipped())})
return rows
}
func GetSeverityColumn(controlSummary reportsummary.IControlSummary) string {
@@ -35,21 +42,21 @@ func GetControlTableHeaders(short bool) []string {
headers[0] = "Controls"
} else {
headers = make([]string, _summaryRowLen)
headers[summaryColumnName] = "Control Name"
headers[summaryColumnCounterFailed] = "Failed Resources"
headers[summaryColumnName] = "Control name"
headers[summaryColumnCounterFailed] = "Failed resources"
headers[summaryColumnCounterAll] = "All Resources"
headers[summaryColumnSeverity] = "Severity"
headers[summaryColumnComplianceScore] = "% Compliance-Score"
headers[summaryColumnComplianceScore] = "Compliance score"
}
return headers
}
func GetColumnsAlignments() []int {
alignments := make([]int, _summaryRowLen)
alignments[summaryColumnSeverity] = tablewriter.ALIGN_CENTER
alignments[summaryColumnName] = tablewriter.ALIGN_LEFT
alignments[summaryColumnCounterFailed] = tablewriter.ALIGN_CENTER
alignments[summaryColumnCounterAll] = tablewriter.ALIGN_CENTER
alignments[summaryColumnSeverity] = tablewriter.ALIGN_LEFT
alignments[summaryColumnComplianceScore] = tablewriter.ALIGN_CENTER
return alignments
}
@@ -8,7 +8,7 @@ import (
func TestWorkloadScan_InitCategoryTableData(t *testing.T) {
expectedHeader := []string{"", "Control Name", "Docs"}
expectedHeader := []string{"", "Control name", "Docs"}
expectedAlign := []int{tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER}
workloadPrinter := NewWorkloadPrinter()
@@ -70,7 +70,7 @@ func getImageScanningHeaders() []string {
headers[imageColumnName] = "Vulnerability"
headers[imageColumnComponent] = "Component"
headers[imageColumnVersion] = "Version"
headers[imageColumnFixedIn] = "Fixed In"
headers[imageColumnFixedIn] = "Fixed in"
return headers
}
@@ -140,7 +140,7 @@ func TestGenerateRow(t *testing.T) {
func TestGetImageScanningHeaders(t *testing.T) {
headers := getImageScanningHeaders()
expectedHeaders := []string{"Severity", "Vulnerability", "Component", "Version", "Fixed In"}
expectedHeaders := []string{"Severity", "Vulnerability", "Component", "Version", "Fixed in"}
for i := range headers {
if headers[i] != expectedHeaders[i] {
@@ -5,6 +5,7 @@ import (
"io"
"os"
"github.com/enescakir/emoji"
"github.com/jwalton/gchalk"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/opa-utils/reporthandling/apis"
@@ -90,16 +91,15 @@ func ImageSeverityToInt(severity string) int {
func FrameworksScoresToString(frameworks []reportsummary.IFrameworkSummary) string {
if len(frameworks) == 1 {
if frameworks[0].GetName() != "" {
return fmt.Sprintf("FRAMEWORK %s\n", frameworks[0].GetName())
// cautils.InfoTextDisplay(prettyPrinter.writer, ))
return fmt.Sprintf("Framework scanned: %s\n", frameworks[0].GetName())
}
} else if len(frameworks) > 1 {
p := "FRAMEWORKS: "
p := "Frameworks scanned: "
i := 0
for ; i < len(frameworks)-1; i++ {
p += fmt.Sprintf("%s (compliance: %.2f), ", frameworks[i].GetName(), frameworks[i].GetComplianceScore())
p += fmt.Sprintf("%s (compliance score: %.2f%%), ", frameworks[i].GetName(), frameworks[i].GetComplianceScore())
}
p += fmt.Sprintf("%s (compliance: %.2f)\n", frameworks[i].GetName(), frameworks[i].GetComplianceScore())
p += fmt.Sprintf("%s (compliance score: %.2f%%)\n", frameworks[i].GetName(), frameworks[i].GetComplianceScore())
return p
}
return ""
@@ -108,7 +108,7 @@ func FrameworksScoresToString(frameworks []reportsummary.IFrameworkSummary) stri
func PrintInfo(writer io.Writer, infoToPrintInfo []InfoStars) {
fmt.Println()
for i := range infoToPrintInfo {
cautils.InfoDisplay(writer, fmt.Sprintf("%s %s\n", infoToPrintInfo[i].Stars, infoToPrintInfo[i].Info))
cautils.InfoDisplay(writer, fmt.Sprintf("%s %s %s\n", emoji.PoliceCarLight, infoToPrintInfo[i].Stars, infoToPrintInfo[i].Info))
}
}
@@ -20,7 +20,7 @@ const (
configScanVerboseRunText = "Run with '--verbose'/'-v' flag for detailed resources view"
imageScanVerboseRunText = "Run with '--verbose'/'-v' flag for detailed vulnerabilities view"
runCommandsText = "Run one of the suggested commands to learn more about a failed control failure"
ksHelmChartLink = "https://github.com/kubescape/helm-charts/tree/main/charts/kubescape-operator"
ksHelmChartLink = "https://kubescape.io/docs/install-operator/"
highStakesWlsText = "High-stakes workloads are defined as those which Kubescape estimates would have the highest impact if they were to be exploited.\n\n"
)
@@ -55,7 +55,7 @@ func getWorkloadPrefixForCmd(namespace, kind, name string) string {
func getTopWorkloadsTitle(topWLsLen int) string {
if topWLsLen > 0 {
return "Highest-stake workloads\n"
return "Highest-stake workloads"
}
return ""
}
@@ -167,24 +167,23 @@ func printTopComponents(writer *os.File, summary imageprinter.ImageScanSummary)
}
txt := "Components with most vulnerabilities"
cautils.InfoTextDisplay(writer, "\n"+txt+"\n")
cautils.SimpleDisplay(writer, strings.Repeat("─", len(txt))+"\n")
cautils.SectionHeadingDisplay(writer, txt)
sortedPkgScores := getSortPackageScores(summary.PackageScores)
for i := 0; i < len(sortedPkgScores) && i < TopPackagesNumber; i++ {
topPkg := summary.PackageScores[sortedPkgScores[i]]
output := fmt.Sprintf(" * %s (%s) -", topPkg.Name, topPkg.Version)
output := fmt.Sprintf("%s (%s) -", topPkg.Name, topPkg.Version)
sortedCVEs := getSortedCVEsBySeverity(topPkg.MapSeverityToCVEsNumber)
for j := range sortedCVEs {
output += fmt.Sprintf(" %d %s,", topPkg.MapSeverityToCVEsNumber[sortedCVEs[j]], sortedCVEs[j])
output += fmt.Sprintf(" %d %s,", topPkg.MapSeverityToCVEsNumber[sortedCVEs[j]], utils.GetColorForVulnerabilitySeverity(sortedCVEs[j])(sortedCVEs[j]))
}
output = output[:len(output)-1]
cautils.SimpleDisplay(writer, output+"\n")
cautils.StarDisplay(writer, output+"\n")
}
cautils.SimpleDisplay(writer, "\n")
@@ -206,25 +205,26 @@ func printImageScanningSummary(writer *os.File, summary imageprinter.ImageScanSu
if len(summary.CVEs) == 0 {
txt := "No vulnerabilities were found!"
cautils.InfoTextDisplay(writer, txt+"\n")
cautils.SimpleDisplay(writer, strings.Repeat("─", len(txt))+"\n")
cautils.InfoDisplay(writer, txt+"\n")
return
}
txt := fmt.Sprintf("%d vulnerabilities found:", len(summary.CVEs))
cautils.InfoTextDisplay(writer, txt+"\n")
cautils.SimpleDisplay(writer, strings.Repeat("─", len(txt))+"\n")
txt := fmt.Sprintf("%d vulnerabilities found", len(summary.CVEs))
cautils.SectionHeadingDisplay(writer, txt)
if len(summary.Images) == 1 {
cautils.SimpleDisplay(writer, "Image: %s\n", summary.Images[0])
cautils.SimpleDisplay(writer, "Image: %s\n\n", summary.Images[0])
} else if len(summary.Images) < 4 {
cautils.SimpleDisplay(writer, "Images: %s\n", strings.Join(summary.Images, ", "))
cautils.SimpleDisplay(writer, "Images: %s\n\n", strings.Join(summary.Images, ", "))
}
for _, k := range keys {
cautils.SimpleDisplay(writer, " * %d %s \n", mapSeverityTSummary[k].NumberOfCVEs, utils.GetColorForVulnerabilitySeverity(k)(k))
cautils.StarDisplay(writer, "%d %s \n", mapSeverityTSummary[k].NumberOfCVEs, utils.GetColorForVulnerabilitySeverity(k)(k))
}
cautils.SimpleDisplay(writer, "\n")
}
func printImagesCommands(writer *os.File, summary imageprinter.ImageScanSummary) {
@@ -233,21 +233,19 @@ func printImagesCommands(writer *os.File, summary imageprinter.ImageScanSummary)
} else {
for _, img := range summary.Images {
imgWithoutTag := strings.Split(img, ":")[0]
cautils.SimpleDisplay(writer, fmt.Sprintf("Receive full report for %s image by running: %s\n", imgWithoutTag, getCallToActionString(fmt.Sprintf("'$ kubescape scan image %s'", img))))
cautils.SimpleDisplay(writer, fmt.Sprintf("Receive a full report for %s by running: %s\n", imgWithoutTag, getCallToActionString(fmt.Sprintf("'$ kubescape scan image %s'", img))))
}
}
cautils.InfoTextDisplay(writer, "\n")
cautils.SimpleDisplay(writer, "\n")
}
func printNextSteps(writer *os.File, nextSteps []string, addLine bool) {
txt := "What now?"
cautils.InfoTextDisplay(writer, fmt.Sprintf("%s\n", txt))
cautils.SimpleDisplay(writer, fmt.Sprintf("%s\n", strings.Repeat("─", len(txt))))
cautils.SectionHeadingDisplay(writer, txt)
for _, ns := range nextSteps {
cautils.SimpleDisplay(writer, "* "+ns+"\n")
cautils.StarDisplay(writer, ns+"\n")
}
if addLine {
cautils.SimpleDisplay(writer, "\n")
@@ -256,21 +254,18 @@ func printNextSteps(writer *os.File, nextSteps []string, addLine bool) {
func printComplianceScore(writer *os.File, frameworks []reportsummary.IFrameworkSummary) {
txt := "Compliance Score"
cautils.InfoTextDisplay(writer, fmt.Sprintf("%s\n", txt))
cautils.SimpleDisplay(writer, fmt.Sprintf("%s\n", strings.Repeat("─", len(txt))))
cautils.SectionHeadingDisplay(writer, txt)
cautils.SimpleDisplay(writer, "The compliance score is calculated by multiplying control failures by the number of failures against supported compliance frameworks. Remediate controls, or configure your cluster baseline with exceptions, to improve this score.\n\n")
for _, fw := range frameworks {
cautils.SimpleDisplay(writer, "* %s: %s", fw.GetName(), gchalk.WithYellow().Bold(fmt.Sprintf("%.2f%%\n", fw.GetComplianceScore())))
cautils.StarDisplay(writer, "%s: %s", fw.GetName(), gchalk.WithBrightYellow().Bold(fmt.Sprintf("%.2f%%\n", fw.GetComplianceScore())))
}
cautils.SimpleDisplay(writer, fmt.Sprintf("\nView a full compliance report by running %s or %s\n", getCallToActionString("'$ kubescape scan framework nsa'"), getCallToActionString("'$ kubescape scan framework mitre'")))
cautils.InfoTextDisplay(writer, "\n")
cautils.SimpleDisplay(writer, "\n")
}
func getCallToActionString(action string) string {
return gchalk.WithBrightBlue().Bold(action)
return gchalk.WithBrightWhite().Bold(action)
}
@@ -88,13 +88,13 @@ func TestGetTopWorkloadsTitle(t *testing.T) {
assert.Equal(t, "", title)
title = getTopWorkloadsTitle(1)
assert.Equal(t, "Highest-stake workloads\n", title)
assert.Equal(t, "Highest-stake workloads", title)
title = getTopWorkloadsTitle(2)
assert.Equal(t, "Highest-stake workloads\n", title)
assert.Equal(t, "Highest-stake workloads", title)
title = getTopWorkloadsTitle(10)
assert.Equal(t, "Highest-stake workloads\n", title)
assert.Equal(t, "Highest-stake workloads", title)
}
func TestGetSeverityToSummaryMap(t *testing.T) {
@@ -119,9 +119,9 @@ func generateResourceHeader(short bool) []string {
} else {
headers = make([]string, _resourceRowLen)
headers[resourceColumnSeverity] = "Severity"
headers[resourceColumnName] = "Control Name"
headers[resourceColumnName] = "Control name"
headers[resourceColumnURL] = "Docs"
headers[resourceColumnPath] = "Assisted Remediation"
headers[resourceColumnPath] = "Assisted remediation"
}
return headers
}
@@ -5,7 +5,6 @@ import (
"encoding/json"
"fmt"
"os"
"strings"
"time"
"github.com/armosec/armoapi-go/apis"
@@ -275,10 +274,7 @@ func (report *ReportEventReceiver) DisplayMessage() {
// print if logger level is lower than warning (debug/info)
if report.message != "" && helpers.ToLevel(logger.L().GetLevel()) < helpers.WarningLevel {
txt := "View results"
cautils.InfoTextDisplay(os.Stderr, fmt.Sprintf("\n%s\n", txt))
cautils.SimpleDisplay(os.Stderr, strings.Repeat("─", len(txt)))
cautils.SimpleDisplay(os.Stderr, fmt.Sprintf("\n%s\n\n", report.message))
cautils.SectionHeadingDisplay(os.Stdout, txt)
cautils.SimpleDisplay(os.Stdout, fmt.Sprintf("%s\n\n", report.message))
}
}
@@ -88,7 +88,7 @@ func TestDisplayMessage(t *testing.T) {
getter.GetKSCloudAPIConnector(),
)
capture, clean := captureStderr(t)
capture, clean := captureStdout(t)
defer clean()
reporter.DisplayMessage()
@@ -114,7 +114,7 @@ func TestDisplayMessage(t *testing.T) {
)
reporter.setMessage("message returned from server")
capture, clean := captureStderr(t)
capture, clean := captureStdout(t)
defer clean()
reporter.DisplayMessage()
@@ -232,7 +232,7 @@ func TestSubmit(t *testing.T) {
opaSession := mockOPASessionObj(t)
capture, clean := captureStderr(t)
capture, clean := captureStdout(t)
if pretty, ok := logger.L().(*prettylogger.PrettyLogger); ok {
pretty.SetWriter(capture)
}
@@ -274,7 +274,7 @@ func TestSubmit(t *testing.T) {
opaSession := mockOPASessionObj(t)
opaSession.Metadata.ScanMetadata.ScanningTarget = reporthandlingv2.Cluster
capture, clean := captureStderr(t)
capture, clean := captureStdout(t)
if pretty, ok := logger.L().(*prettylogger.PrettyLogger); ok {
pretty.SetWriter(capture)
}
@@ -357,3 +357,25 @@ func captureStderr(t testing.TB) (*os.File, func()) {
mxStdio.Unlock()
}
}
func captureStdout(t testing.TB) (*os.File, func()) {
mxStdio.Lock()
saved := os.Stdout
capture, err := os.CreateTemp("", "stdout")
if !assert.NoError(t, err) {
mxStdio.Unlock()
t.FailNow()
return nil, nil
}
os.Stdout = capture
return capture, func() {
_ = capture.Close()
_ = os.Remove(capture.Name())
os.Stdout = saved
mxStdio.Unlock()
}
}