Improve cluster scan cli (#1352)

* start improvements

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

* cta

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

* refactor

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

* fixes

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

* http handler go mod

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

* set control type

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

* move to func

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

* move to func

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

* use color for vuln summary

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>

---------

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>
Co-authored-by: Daniel Grunberger <danielgrunberger@armosec.io>
This commit is contained in:
Daniel Grunberger
2023-08-22 15:21:01 +03:00
committed by GitHub
parent b67fd95e31
commit 7b46cdd480
28 changed files with 276 additions and 132 deletions

View File

@@ -91,6 +91,7 @@ func getControlCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comman
}
scanInfo.FrameworkScan = false
scanInfo.SetScanType(cautils.ScanTypeControl)
if err := validateControlScanInfo(scanInfo); err != nil {
return err

View File

@@ -33,6 +33,7 @@ const (
ScanTypeImage ScanTypes = "image"
ScanTypeWorkload ScanTypes = "workload"
ScanTypeFramework ScanTypes = "framework"
ScanTypeControl ScanTypes = "control"
)
type OPASessionObj struct {
@@ -56,6 +57,7 @@ type OPASessionObj struct {
Exceptions []armotypes.PostureExceptionPolicy // list of exceptions to apply on scan results
OmitRawResources bool // omit raw resources from output
SingleResourceScan workloadinterface.IWorkload // single resource scan
TopWorkloadsByScore []reporthandling.IResource
}
func NewOPASessionObj(ctx context.Context, frameworks []reporthandling.Framework, k8sResources K8SResources, scanInfo *ScanInfo) *OPASessionObj {
@@ -109,7 +111,7 @@ func (sessionObj *OPASessionObj) SetTopWorkloads() {
Source: &source,
}
sessionObj.Report.SummaryDetails.TopWorkloadsByScore = append(sessionObj.Report.SummaryDetails.TopWorkloadsByScore, wlObj)
sessionObj.TopWorkloadsByScore = append(sessionObj.TopWorkloadsByScore, wlObj)
count++
}
}

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 you are updated to latest version")
logger.L().Ctx(ctx).Warning("Unknown build number, this might affect your scan results. Please make sure that you are running the latest version")
}
if v, ok := os.LookupEnv(CLIENT_ENV); ok && v != "" {

View File

@@ -168,8 +168,8 @@ func setSubmitBehavior(scanInfo *cautils.ScanInfo, tenantConfig cautils.ITenantC
return
}
// do not submit control scanning
if !scanInfo.FrameworkScan {
// do not submit control/workload scanning
if !isScanTypeForSubmission(scanInfo.ScanType) {
scanInfo.Submit = false
return
}
@@ -179,12 +179,6 @@ func setSubmitBehavior(scanInfo *cautils.ScanInfo, tenantConfig cautils.ITenantC
return
}
// do not submit single resource scan to BE
if scanInfo.ScanObject != nil {
scanInfo.Submit = false
return
}
// If There is no account, or if the account is not legal, do not submit
if _, err := uuid.Parse(tenantConfig.GetAccountID()); err != nil {
scanInfo.Submit = false
@@ -198,6 +192,13 @@ func setSubmitBehavior(scanInfo *cautils.ScanInfo, tenantConfig cautils.ITenantC
}
func isScanTypeForSubmission(scanType cautils.ScanTypes) bool {
if scanType == cautils.ScanTypeControl || scanType == cautils.ScanTypeWorkload {
return false
}
return true
}
// setPolicyGetter set the policy getter - local file/github release/Kubescape Cloud API
func getPolicyGetter(ctx context.Context, loadPoliciesFromFile []string, tenantEmail string, frameworkScope bool, downloadReleasedPolicy *getter.DownloadReleasedPolicy) getter.IPolicyGetter {
if len(loadPoliciesFromFile) > 0 {

View File

@@ -183,3 +183,49 @@ func TestGetSensorHandler(t *testing.T) {
// TODO(fredbi): need to share the k8s client mock to test a happy path / deployment failure path
}
func TestIsScanTypeForSubmission(t *testing.T) {
test := []struct {
name string
scanType cautils.ScanTypes
want bool
}{
{
name: "cluster scan",
scanType: cautils.ScanTypeCluster,
want: true,
},
{
name: "repo scan",
scanType: cautils.ScanTypeRepo,
want: true,
},
{
name: "workload scan",
scanType: cautils.ScanTypeWorkload,
want: false,
},
{
name: "control scan",
scanType: cautils.ScanTypeControl,
want: false,
},
{
name: "framework scan",
scanType: cautils.ScanTypeFramework,
want: true,
},
{
name: "image scan",
scanType: cautils.ScanTypeImage,
want: true,
},
}
for _, tt := range test {
t.Run(tt.name, func(t *testing.T) {
got := isScanTypeForSubmission(tt.scanType)
assert.Equal(t, tt.want, got)
})
}
}

View File

@@ -116,7 +116,11 @@ func (pp *PrettyPrinter) PrintImageScan(imageScanData []cautils.ImageScanData) {
func (pp *PrettyPrinter) ActionPrint(_ context.Context, opaSessionObj *cautils.OPASessionObj, imageScanData []cautils.ImageScanData) {
if opaSessionObj != nil {
fmt.Fprintf(pp.writer, "\n"+getSeparator("^")+"\n")
if isPrintSeparatorType(pp.scanType) {
fmt.Fprintf(pp.writer, "\n"+getSeparator("^")+"\n")
} else {
fmt.Fprintf(pp.writer, "\n")
}
sortedControlIDs := getSortedControlsIDs(opaSessionObj.Report.SummaryDetails.Controls) // ListControls().All())
@@ -131,7 +135,7 @@ func (pp *PrettyPrinter) ActionPrint(_ context.Context, opaSessionObj *cautils.O
pp.printOverview(opaSessionObj, pp.verboseMode)
pp.mainPrinter.PrintConfigurationsScanning(&opaSessionObj.Report.SummaryDetails, sortedControlIDs)
pp.mainPrinter.PrintConfigurationsScanning(&opaSessionObj.Report.SummaryDetails, sortedControlIDs, opaSessionObj.TopWorkloadsByScore)
// When writing to Stdout, we arent really writing to an output file,
// so no need to print that we are
@@ -157,7 +161,8 @@ func (pp *PrettyPrinter) printOverview(opaSessionObj *cautils.OPASessionObj, pri
func (pp *PrettyPrinter) printHeader(opaSessionObj *cautils.OPASessionObj) {
if pp.scanType == cautils.ScanTypeCluster || pp.scanType == cautils.ScanTypeRepo {
cautils.InfoDisplay(pp.writer, "\nSecurity Overview\n\n")
cautils.InfoDisplay(pp.writer, fmt.Sprintf("\nKubescape security posture overview for cluster: %s\n\n", cautils.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 {
ns := opaSessionObj.SingleResourceScan.GetNamespace()
if ns == "" {
@@ -330,3 +335,12 @@ func getSeparator(sep string) string {
}
return s
}
func isPrintSeparatorType(scanType cautils.ScanTypes) bool {
switch scanType {
case cautils.ScanTypeCluster, cautils.ScanTypeRepo, cautils.ScanTypeImage, cautils.ScanTypeWorkload:
return false
default:
return true
}
}

View File

@@ -3,10 +3,12 @@ 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"
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/imageprinter"
"github.com/kubescape/opa-utils/reporthandling"
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
)
@@ -29,16 +31,15 @@ func (cp *ClusterPrinter) PrintImageScanning(summary *imageprinter.ImageScanSumm
printImagesCommands(cp.writer, *summary)
}
func (cp *ClusterPrinter) PrintConfigurationsScanning(summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) {
func (cp *ClusterPrinter) PrintConfigurationsScanning(summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string, topWorkloadsByScore []reporthandling.IResource) {
cp.categoriesTablePrinter.PrintCategoriesTables(cp.writer, summaryDetails, sortedControlIDs)
printComplianceScore(cp.writer, filterComplianceFrameworks(summaryDetails.ListFrameworks()))
if len(summaryDetails.TopWorkloadsByScore) > 0 {
cp.printTopWorkloads(summaryDetails)
if len(topWorkloadsByScore) > 0 {
cp.printTopWorkloads(topWorkloadsByScore)
}
printComplianceScore(cp.writer, filterComplianceFrameworks(summaryDetails.ListFrameworks()))
}
func (cp *ClusterPrinter) PrintNextSteps() {
@@ -47,16 +48,22 @@ func (cp *ClusterPrinter) PrintNextSteps() {
func (cp *ClusterPrinter) getNextSteps() []string {
return []string{
configScanVerboseRunText,
installHelmText,
CICDSetupText,
runCommandsText,
scanWorkloadText,
installKubescapeText,
}
}
func (cp *ClusterPrinter) printTopWorkloads(summaryDetails *reportsummary.SummaryDetails) {
cautils.InfoTextDisplay(cp.writer, getTopWorkloadsTitle(len(summaryDetails.TopWorkloadsByScore)))
func (cp *ClusterPrinter) printTopWorkloads(topWorkloadsByScore []reporthandling.IResource) {
txt := getTopWorkloadsTitle(len(topWorkloadsByScore))
for i, wl := range summaryDetails.TopWorkloadsByScore {
cautils.InfoTextDisplay(cp.writer, txt)
cautils.SimpleDisplay(cp.writer, fmt.Sprintf("%s\n\n", strings.Repeat("─", len(txt))))
cautils.SimpleDisplay(cp.writer, highStakesWlsText)
for i, wl := range topWorkloadsByScore {
ns := wl.GetNamespace()
name := wl.GetName()
kind := wl.GetKind()

View File

@@ -11,16 +11,16 @@ func TestClusterScan_getNextSteps(t *testing.T) {
t.Errorf("Expected 3 next steps, got %d", len(nextSteps))
}
if nextSteps[0] != configScanVerboseRunText {
if nextSteps[0] != runCommandsText {
t.Errorf("Expected %s, got %s", configScanVerboseRunText, nextSteps[0])
}
if nextSteps[1] != installHelmText {
t.Errorf("Expected %s, got %s", installHelmText, nextSteps[1])
if nextSteps[1] != scanWorkloadText {
t.Errorf("Expected %s, got %s", scanWorkloadText, nextSteps[1])
}
if nextSteps[2] != CICDSetupText {
t.Errorf("Expected %s, got %s", CICDSetupText, nextSteps[2])
if nextSteps[2] != installKubescapeText {
t.Errorf("Expected %s, got %s", installKubescapeText, nextSteps[2])
}
}

View File

@@ -5,6 +5,7 @@ import (
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter"
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/imageprinter"
"github.com/kubescape/opa-utils/reporthandling"
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
)
@@ -34,6 +35,6 @@ func (sp *SummaryPrinter) getVerboseMode() bool {
return sp.verboseMode
}
func (sp *SummaryPrinter) PrintConfigurationsScanning(summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) {
func (sp *SummaryPrinter) PrintConfigurationsScanning(summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string, topWorkloadsByScore []reporthandling.IResource) {
sp.summaryTablePrinter.PrintSummaryTable(sp.writer, summaryDetails, sortedControlIDs)
}

View File

@@ -5,6 +5,7 @@ import (
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/imageprinter"
"github.com/kubescape/opa-utils/reporthandling"
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
)
@@ -43,13 +44,13 @@ func (ip *ImagePrinter) PrintImageScanningTable(summary imageprinter.ImageScanSu
cautils.InfoTextDisplay(ip.writer, "\n")
}
func (ip *ImagePrinter) PrintConfigurationsScanning(summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) {
func (ip *ImagePrinter) PrintConfigurationsScanning(summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string, topWorkloadsByScore []reporthandling.IResource) {
}
func (ip *ImagePrinter) PrintNextSteps() {
if ip.verboseMode {
printNextSteps(ip.writer, []string{CICDSetupText, installHelmText}, true)
printNextSteps(ip.writer, []string{installKubescapeText}, true)
return
}
printNextSteps(ip.writer, []string{imageScanVerboseRunText, CICDSetupText, installHelmText}, true)
printNextSteps(ip.writer, []string{imageScanVerboseRunText, installKubescapeText}, true)
}

View File

@@ -2,11 +2,12 @@ package prettyprinter
import (
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/imageprinter"
"github.com/kubescape/opa-utils/reporthandling"
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
)
type MainPrinter interface {
PrintConfigurationsScanning(summaryDetails *reportsummary.SummaryDetails, sortedControls [][]string)
PrintConfigurationsScanning(summaryDetails *reportsummary.SummaryDetails, sortedControls [][]string, topWorkloadsByScore []reporthandling.IResource)
PrintImageScanning(imageScanSummary *imageprinter.ImageScanSummary)
PrintNextSteps()
}

View File

@@ -3,6 +3,7 @@ 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"
@@ -32,11 +33,11 @@ func (rp *RepoPrinter) PrintImageScanning(summary *imageprinter.ImageScanSummary
printTopComponents(rp.writer, *summary)
}
func (rp *RepoPrinter) PrintConfigurationsScanning(summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) {
func (rp *RepoPrinter) PrintConfigurationsScanning(summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string, topWorkloadsByScore []reporthandling.IResource) {
rp.categoriesTablePrinter.PrintCategoriesTables(rp.writer, summaryDetails, sortedControlIDs)
if len(summaryDetails.TopWorkloadsByScore) > 1 {
rp.printTopWorkloads(summaryDetails)
if len(topWorkloadsByScore) > 1 {
rp.printTopWorkloads(topWorkloadsByScore)
}
}
@@ -47,17 +48,22 @@ func (rp *RepoPrinter) PrintNextSteps() {
func (rp *RepoPrinter) getNextSteps() []string {
return []string{
configScanVerboseRunText,
runCommandsText,
clusterScanRunText,
CICDSetupText,
installHelmText,
scanWorkloadText,
installKubescapeText,
}
}
func (rp *RepoPrinter) printTopWorkloads(summaryDetails *reportsummary.SummaryDetails) {
cautils.InfoTextDisplay(rp.writer, getTopWorkloadsTitle(len(summaryDetails.TopWorkloadsByScore)))
func (rp *RepoPrinter) printTopWorkloads(topWorkloadsByScore []reporthandling.IResource) {
txt := getTopWorkloadsTitle(len(topWorkloadsByScore))
cautils.InfoTextDisplay(rp.writer, txt)
for i, wl := range summaryDetails.TopWorkloadsByScore {
cautils.SimpleDisplay(rp.writer, fmt.Sprintf("%s\n\n", strings.Repeat("─", len(txt))))
cautils.SimpleDisplay(rp.writer, highStakesWlsText)
for i, wl := range topWorkloadsByScore {
ns := wl.GetNamespace()
name := wl.GetName()
kind := wl.GetKind()

View File

@@ -15,20 +15,20 @@ func TestRepoScan_getNextSteps(t *testing.T) {
t.Errorf("Expected 4 next steps, got %d", len(nextSteps))
}
if nextSteps[0] != configScanVerboseRunText {
t.Errorf("Expected %s, got %s", configScanVerboseRunText, nextSteps[0])
if nextSteps[0] != runCommandsText {
t.Errorf("Expected %s, got %s", clusterScanRunText, nextSteps[0])
}
if nextSteps[1] != clusterScanRunText {
t.Errorf("Expected %s, got %s", clusterScanRunText, nextSteps[1])
t.Errorf("Expected %s, got %s", runCommandsText, nextSteps[1])
}
if nextSteps[2] != CICDSetupText {
t.Errorf("Expected %s, got %s", CICDSetupText, nextSteps[2])
if nextSteps[2] != scanWorkloadText {
t.Errorf("Expected %s, got %s", scanWorkloadText, nextSteps[2])
}
if nextSteps[3] != installHelmText {
t.Errorf("Expected %s, got %s", installHelmText, nextSteps[3])
if nextSteps[3] != installKubescapeText {
t.Errorf("Expected %s, got %s", installKubescapeText, nextSteps[3])
}
}

View File

@@ -18,7 +18,7 @@ const (
statusHeader = "STATUS"
docsHeader = "DOCS"
resourcesHeader = "RESOURCES"
runHeader = "RUN"
runHeader = "VIEW DETAILS"
)
// initializes the table headers and column alignments based on the category type

View File

@@ -21,7 +21,7 @@ func TestInitCategoryTableData(t *testing.T) {
{
name: "Test1",
categoryType: TypeCounting,
expectedHeaders: []string{"CONTROL NAME", "RESOURCES", "RUN"},
expectedHeaders: []string{"CONTROL NAME", "RESOURCES", "VIEW DETAILS"},
expectedAlignments: []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT},
},
{

View File

@@ -6,9 +6,7 @@ import (
"strings"
v5 "github.com/anchore/grype/grype/db/v5"
"github.com/jwalton/gchalk"
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils"
"github.com/kubescape/opa-utils/reporthandling/apis"
"github.com/olekukonko/tablewriter"
)
@@ -47,7 +45,7 @@ func generateRows(summary ImageScanSummary) [][]string {
func generateRow(cve CVE) []string {
row := make([]string, 5)
row[imageColumnSeverity] = getColor(cve.Severity)(cve.Severity)
row[imageColumnSeverity] = utils.GetColorForVulnerabilitySeverity(cve.Severity)(cve.Severity)
row[imageColumnName] = cve.ID
row[imageColumnComponent] = cve.Package
row[imageColumnVersion] = cve.Version
@@ -76,20 +74,3 @@ func getImageScanningHeaders() []string {
func getImageScanningColumnsAlignments() []int {
return []int{tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT}
}
func getColor(severity string) func(...string) string {
switch severity {
case apis.SeverityCriticalString:
return gchalk.WithAnsi256(1).Bold
case apis.SeverityHighString:
return gchalk.WithAnsi256(196).Bold
case apis.SeverityMediumString:
return gchalk.WithAnsi256(166).Bold
case apis.SeverityLowString:
return gchalk.WithAnsi256(220).Bold
case apis.SeverityNegligibleString:
return gchalk.WithAnsi256(39).Bold
default:
return gchalk.WithAnsi256(30).Bold
}
}

View File

@@ -55,7 +55,7 @@ func MapInfoToPrintInfo(controls reportsummary.ControlSummaries) []InfoStars {
return infoToPrintInfo
}
func GetColor(severity int) (func(...string) string) {
func GetColor(severity int) func(...string) string {
switch severity {
case apis.SeverityCritical:
return gchalk.WithAnsi256(1).Bold
@@ -112,7 +112,7 @@ func PrintInfo(writer io.Writer, infoToPrintInfo []InfoStars) {
}
}
func GetStatusColor(status apis.ScanningStatus) (func(...string) string) {
func GetStatusColor(status apis.ScanningStatus) func(...string) string {
switch status {
case apis.StatusPassed:
return gchalk.WithGreen().Bold
@@ -125,7 +125,7 @@ func GetStatusColor(status apis.ScanningStatus) (func(...string) string) {
}
}
func getColor(controlSeverity int) (func(...string) string) {
func getColor(controlSeverity int) func(...string) string {
switch controlSeverity {
case apis.SeverityCritical:
return gchalk.WithAnsi256(1).Bold
@@ -168,3 +168,22 @@ func CheckShortTerminalWidth(rows [][]string, headers []string) bool {
}
return termWidth <= maxWidth
}
func GetColorForVulnerabilitySeverity(severity string) func(...string) string {
switch severity {
case apis.SeverityCriticalString:
return gchalk.WithAnsi256(1).Bold
case apis.SeverityHighString:
return gchalk.WithAnsi256(196).Bold
case apis.SeverityMediumString:
return gchalk.WithAnsi256(166).Bold
case apis.SeverityLowString:
return gchalk.WithAnsi256(220).Bold
case apis.SeverityNegligibleString:
return gchalk.WithAnsi256(39).Bold
case apis.SeverityUnknownString:
return gchalk.WithAnsi256(30).Bold
default:
return gchalk.WithAnsi256(7).Bold
}
}

View File

@@ -17,16 +17,17 @@ import (
)
const (
linkToHelm = "https://github.com/kubescape/helm-charts"
linkToCICDSetup = "https://hub.armosec.io/docs/integrations"
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-cloud-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"
)
var (
scanWorkloadText = fmt.Sprintf("Scan a workload with %s to see vulnerability information", getCallToActionString("'$ kubescape scan workload'"))
installKubescapeText = fmt.Sprintf("Install Kubescape in your cluster for continuous monitoring and a full vulnerability report: %s", ksHelmChartLink)
clusterScanRunText = fmt.Sprintf("Run a cluster scan: %s", getCallToActionString("'$ kubescape scan'"))
installHelmText = fmt.Sprintf("Install Kubescape in your cluster for continuous monitoring: %s", linkToHelm)
CICDSetupText = fmt.Sprintf("Add Kubescape to your CI/CD pipeline: %s", linkToCICDSetup)
complianceFrameworks = []string{"nsa", "mitre"}
cveSeverities = []string{"Critical", "High", "Medium", "Low", "Negligible", "Unknown"}
)
@@ -53,11 +54,8 @@ func getWorkloadPrefixForCmd(namespace, kind, name string) string {
}
func getTopWorkloadsTitle(topWLsLen int) string {
if topWLsLen > 1 {
return "Your highest stake workloads:\n"
}
if topWLsLen > 0 {
return "Your highest stake workload:\n"
return "Highest-stake workloads\n"
}
return ""
}
@@ -218,28 +216,32 @@ func printImageScanningSummary(writer *os.File, summary imageprinter.ImageScanSu
}
for _, k := range keys {
if k == "Other" {
cautils.SimpleDisplay(writer, " * %d %s \n", mapSeverityTSummary[k].NumberOfCVEs, k)
} else {
cautils.SimpleDisplay(writer, " * %d %s\n", mapSeverityTSummary[k].NumberOfCVEs, k)
}
cautils.SimpleDisplay(writer, " * %d %s \n", mapSeverityTSummary[k].NumberOfCVEs, utils.GetColorForVulnerabilitySeverity(k)(k))
}
}
func printImagesCommands(writer *os.File, summary imageprinter.ImageScanSummary) {
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))))
if len(summary.Images) > 3 {
cautils.SimpleDisplay(writer, "Receive full report by running: kubescape scan image <image>\n")
} 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.InfoTextDisplay(writer, "\n")
}
func printNextSteps(writer *os.File, nextSteps []string, addLine bool) {
cautils.InfoTextDisplay(writer, "Follow-up steps:\n")
txt := "What now?"
cautils.InfoTextDisplay(writer, fmt.Sprintf("%s\n", txt))
cautils.SimpleDisplay(writer, fmt.Sprintf("%s\n\n", strings.Repeat("─", len(txt))))
for _, ns := range nextSteps {
cautils.SimpleDisplay(writer, "- "+ns+"\n")
cautils.SimpleDisplay(writer, "* "+ns+"\n")
}
if addLine {
cautils.SimpleDisplay(writer, "\n")
@@ -247,12 +249,18 @@ func printNextSteps(writer *os.File, nextSteps []string, addLine bool) {
}
func printComplianceScore(writer *os.File, frameworks []reportsummary.IFrameworkSummary) {
cautils.InfoTextDisplay(writer, "Compliance Score:\n")
txt := "Compliance Score"
cautils.InfoTextDisplay(writer, fmt.Sprintf("%s\n", txt))
cautils.SimpleDisplay(writer, fmt.Sprintf("%s\n\n", strings.Repeat("─", len(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: %.2f%%\n", fw.GetName(), fw.GetComplianceScore())
cautils.SimpleDisplay(writer, "* %s: %s", fw.GetName(), gchalk.WithYellow().Bold(fmt.Sprintf("%.2f%%\n", fw.GetComplianceScore())))
}
cautils.SimpleDisplay(writer, fmt.Sprintf("View full compliance report by running: %s\n", getCallToActionString("'$ kubescape scan framework nsa,mitre'")))
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")
}

View File

@@ -88,13 +88,13 @@ func TestGetTopWorkloadsTitle(t *testing.T) {
assert.Equal(t, "", title)
title = getTopWorkloadsTitle(1)
assert.Equal(t, "Your highest stake workload:\n", title)
assert.Equal(t, "Highest-stake workloads\n", title)
title = getTopWorkloadsTitle(2)
assert.Equal(t, "Your highest stake workloads:\n", title)
assert.Equal(t, "Highest-stake workloads\n", title)
title = getTopWorkloadsTitle(10)
assert.Equal(t, "Your highest stake workloads:\n", title)
assert.Equal(t, "Highest-stake workloads\n", title)
}
func TestGetSeverityToSummaryMap(t *testing.T) {

View File

@@ -5,6 +5,7 @@ import (
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter"
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/imageprinter"
"github.com/kubescape/opa-utils/reporthandling"
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
)
@@ -33,13 +34,13 @@ func (wp *WorkloadPrinter) PrintNextSteps() {
func (wp *WorkloadPrinter) getNextSteps() []string {
return []string{
runCommandsText,
configScanVerboseRunText,
installHelmText,
CICDSetupText,
installKubescapeText,
}
}
func (wp *WorkloadPrinter) PrintConfigurationsScanning(summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) {
func (wp *WorkloadPrinter) PrintConfigurationsScanning(summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string, topWorkloadsByScore []reporthandling.IResource) {
wp.categoriesTablePrinter.PrintCategoriesTables(wp.writer, summaryDetails, sortedControlIDs)
}

View File

@@ -11,15 +11,16 @@ func TestWorkloadScan_getNextSteps(t *testing.T) {
t.Errorf("Expected 3 next steps, got %d", len(nextSteps))
}
if nextSteps[0] != configScanVerboseRunText {
if nextSteps[0] != runCommandsText {
t.Errorf("Expected %s, got %s", runCommandsText, nextSteps[0])
}
if nextSteps[1] != configScanVerboseRunText {
t.Errorf("Expected %s, got %s", configScanVerboseRunText, nextSteps[0])
}
if nextSteps[1] != installHelmText {
t.Errorf("Expected %s, got %s", installHelmText, nextSteps[1])
if nextSteps[2] != installKubescapeText {
t.Errorf("Expected %s, got %s", installKubescapeText, nextSteps[1])
}
if nextSteps[2] != CICDSetupText {
t.Errorf("Expected %s, got %s", CICDSetupText, nextSteps[2])
}
}

View File

@@ -1 +1,55 @@
package printer
import (
"testing"
"github.com/kubescape/kubescape/v2/core/cautils"
)
func TestIsPrintSeparatorType(t *testing.T) {
tests := []struct {
name string
expected bool
scanType cautils.ScanTypes
}{
{
name: "cluster scan",
scanType: cautils.ScanTypeCluster,
expected: false,
},
{
name: "repo scan",
scanType: cautils.ScanTypeRepo,
expected: false,
},
{
name: "workload scan",
scanType: cautils.ScanTypeWorkload,
expected: false,
},
{
name: "control scan",
scanType: cautils.ScanTypeControl,
expected: true,
},
{
name: "framework scan",
scanType: cautils.ScanTypeFramework,
expected: true,
},
{
name: "image scan",
scanType: cautils.ScanTypeImage,
expected: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got := isPrintSeparatorType(test.scanType)
if got != test.expected {
t.Errorf("%s failed - expected %t, got %t", test.name, test.expected, got)
}
})
}
}

View File

@@ -7,6 +7,7 @@ import (
"net/http"
"net/url"
"os"
"strings"
"time"
"github.com/armosec/armoapi-go/apis"
@@ -64,7 +65,7 @@ func (report *ReportEventReceiver) Submit(ctx context.Context, opaSessionObj *ca
ctx, span := otel.Tracer("").Start(ctx, "reportEventReceiver.Submit")
defer span.End()
report.reportTime = time.Now().UTC()
if report.customerGUID == "" {
logger.L().Ctx(ctx).Error("failed to publish results. Reason: Unknown account ID. Run kubescape with the '--account <account ID>' flag. Contact ARMO team for more details")
return nil
@@ -249,21 +250,20 @@ func (report *ReportEventReceiver) sendReport(host string, postureReport *report
}
func (report *ReportEventReceiver) generateMessage() {
report.message = ""
sep := "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
report.message = sep
report.message += "Now, take your scan results to the next level with actionable insights on ARMO Platform.\n\n"
report.message += fmt.Sprintf("Sign up for free here - %s\n", report.GetURL())
report.message += sep
report.message = "Your scan results were successfully submitted to ARMO Platform. Take your security posture to the next level with actionable insights and evaluation of your full Kubernetes estate.\n"
report.message += fmt.Sprintf("\nView your results here: %s", report.GetURL())
}
func (report *ReportEventReceiver) DisplayReportURL() {
// print if logger level is lower than warning (debug/info)
if report.message != "" && helpers.ToLevel(logger.L().GetLevel()) < helpers.WarningLevel {
cautils.InfoTextDisplay(os.Stderr, fmt.Sprintf("\n\n%s\n\n", report.message))
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\n%s\n\n", report.message))
}
}

View File

@@ -337,8 +337,8 @@ func TestDisplayReportURL(t *testing.T) {
require.NoError(t, err)
require.NotEmpty(t, buf)
assert.Contains(t, string(buf), "Now")
assert.Contains(t, string(buf), "https://cloud.armosec.io/account/sign-up")
assert.Contains(t, string(buf), "Your scan results were successfully submitted")
assert.Contains(t, string(buf), "View your results here")
t.Log(string(buf))
})

4
go.mod
View File

@@ -8,7 +8,7 @@ require (
github.com/anchore/grype v0.63.1
github.com/anchore/stereoscope v0.0.0-20230627195312-cd49355d934e
github.com/anchore/syft v0.84.1
github.com/armosec/armoapi-go v0.0.212
github.com/armosec/armoapi-go v0.0.220
github.com/armosec/utils-go v0.0.20
github.com/armosec/utils-k8s-go v0.0.16
github.com/briandowns/spinner v1.23.0
@@ -24,7 +24,7 @@ require (
github.com/kubescape/go-git-url v0.0.25
github.com/kubescape/go-logger v0.0.20
github.com/kubescape/k8s-interface v0.0.138
github.com/kubescape/opa-utils v0.0.261
github.com/kubescape/opa-utils v0.0.267
github.com/kubescape/rbac-utils v0.0.21-0.20230806101615-07e36f555520
github.com/kubescape/regolibrary v1.0.291-rc.0
github.com/libgit2/git2go/v33 v33.0.9

8
go.sum
View File

@@ -591,8 +591,8 @@ github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armosec/armoapi-go v0.0.212 h1:QdmCywiA8NRhyynzHJkIYYZBMBb+1pySUhN3jm4b3Fw=
github.com/armosec/armoapi-go v0.0.212/go.mod h1:4AEdwBrbS1YCAn/lZzV+cOOR9BPa0MTHYHiJDlR1uRQ=
github.com/armosec/armoapi-go v0.0.220 h1:gfg2UmcFgcyStjp5ZXfwE8yb0H43eaRX9H/KkqFIv6w=
github.com/armosec/armoapi-go v0.0.220/go.mod h1:Y1ZcqPUTQ+F8JiQzErrToK5ULrPvClxZoshHmV9PIlU=
github.com/armosec/gojay v1.2.15 h1:sSB2vnAvacUNkw9nzUYZKcPzhJOyk6/5LK2JCNdmoZY=
github.com/armosec/gojay v1.2.15/go.mod h1:vzVAaay2TWJAngOpxu8aqLbye9jMgoKleuAOK+xsOts=
github.com/armosec/utils-go v0.0.20 h1:bvr+TMumEYdMsGFGSsaQysST7K02nNROFvuajNuKPlw=
@@ -1296,8 +1296,8 @@ github.com/kubescape/go-logger v0.0.20 h1:ZU3T6Za7maCiChdoTrqpD6TI11DGJwd9xU/TFt
github.com/kubescape/go-logger v0.0.20/go.mod h1:BAWhQMYc/gnC5wMtPvc9Z4VXFqykFFMaXaPkq0+txBY=
github.com/kubescape/k8s-interface v0.0.138 h1:JjqLExOQiV1iG6jDLVQ/KpPzH8T9U7jQOtpUe5frF2o=
github.com/kubescape/k8s-interface v0.0.138/go.mod h1:5sz+5Cjvo98lTbTVDiDA4MmlXxeHSVMW/wR0V3hV4K8=
github.com/kubescape/opa-utils v0.0.261 h1:NEASuRRHfbQRf/9wAEdm+7VV+UDg5tr+VgIfsedbdas=
github.com/kubescape/opa-utils v0.0.261/go.mod h1:0Be6E+vHqjavl/JneqgyC+oXOdfs6s+V6YnFvBkIAsA=
github.com/kubescape/opa-utils v0.0.267 h1:qzINBGsVOTKeLAIj1YfaYdV93FsSRriWdiN0JXJwD/o=
github.com/kubescape/opa-utils v0.0.267/go.mod h1:95JkuIOfClgLc+DyGb2mDvefRW0STkZe4L2z6AaZJlQ=
github.com/kubescape/rbac-utils v0.0.21-0.20230806101615-07e36f555520 h1:SqlwF8G+oFazeYmZQKoPczLEflBQpwpHCU8DoLLyfj8=
github.com/kubescape/rbac-utils v0.0.21-0.20230806101615-07e36f555520/go.mod h1:wuxMUSDzGUyWd25IJfBzEJ/Udmw2Vy7npj+MV3u3GrU=
github.com/kubescape/regolibrary v1.0.291-rc.0 h1:DztPS3NSKfiltO1wZvxRjuu5c99c6+dEgfTs6DcsVa8=

View File

@@ -13,7 +13,7 @@ require (
github.com/kubescape/go-logger v0.0.20
github.com/kubescape/k8s-interface v0.0.138
github.com/kubescape/kubescape/v2 v2.0.0-00010101000000-000000000000
github.com/kubescape/opa-utils v0.0.261
github.com/kubescape/opa-utils v0.0.267
github.com/stretchr/testify v1.8.4
go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.38.0
go.opentelemetry.io/otel v1.16.0
@@ -99,7 +99,7 @@ require (
github.com/anchore/stereoscope v0.0.0-20230627195312-cd49355d934e // indirect
github.com/anchore/syft v0.84.1 // indirect
github.com/andybalholm/brotli v1.0.4 // indirect
github.com/armosec/armoapi-go v0.0.212 // indirect
github.com/armosec/armoapi-go v0.0.220 // indirect
github.com/armosec/gojay v1.2.15 // indirect
github.com/armosec/utils-k8s-go v0.0.16 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect

View File

@@ -591,8 +591,8 @@ github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armosec/armoapi-go v0.0.212 h1:QdmCywiA8NRhyynzHJkIYYZBMBb+1pySUhN3jm4b3Fw=
github.com/armosec/armoapi-go v0.0.212/go.mod h1:4AEdwBrbS1YCAn/lZzV+cOOR9BPa0MTHYHiJDlR1uRQ=
github.com/armosec/armoapi-go v0.0.220 h1:gfg2UmcFgcyStjp5ZXfwE8yb0H43eaRX9H/KkqFIv6w=
github.com/armosec/armoapi-go v0.0.220/go.mod h1:Y1ZcqPUTQ+F8JiQzErrToK5ULrPvClxZoshHmV9PIlU=
github.com/armosec/gojay v1.2.15 h1:sSB2vnAvacUNkw9nzUYZKcPzhJOyk6/5LK2JCNdmoZY=
github.com/armosec/gojay v1.2.15/go.mod h1:vzVAaay2TWJAngOpxu8aqLbye9jMgoKleuAOK+xsOts=
github.com/armosec/utils-go v0.0.20 h1:bvr+TMumEYdMsGFGSsaQysST7K02nNROFvuajNuKPlw=
@@ -1300,8 +1300,8 @@ github.com/kubescape/go-logger v0.0.20 h1:ZU3T6Za7maCiChdoTrqpD6TI11DGJwd9xU/TFt
github.com/kubescape/go-logger v0.0.20/go.mod h1:BAWhQMYc/gnC5wMtPvc9Z4VXFqykFFMaXaPkq0+txBY=
github.com/kubescape/k8s-interface v0.0.138 h1:JjqLExOQiV1iG6jDLVQ/KpPzH8T9U7jQOtpUe5frF2o=
github.com/kubescape/k8s-interface v0.0.138/go.mod h1:5sz+5Cjvo98lTbTVDiDA4MmlXxeHSVMW/wR0V3hV4K8=
github.com/kubescape/opa-utils v0.0.261 h1:NEASuRRHfbQRf/9wAEdm+7VV+UDg5tr+VgIfsedbdas=
github.com/kubescape/opa-utils v0.0.261/go.mod h1:0Be6E+vHqjavl/JneqgyC+oXOdfs6s+V6YnFvBkIAsA=
github.com/kubescape/opa-utils v0.0.267 h1:qzINBGsVOTKeLAIj1YfaYdV93FsSRriWdiN0JXJwD/o=
github.com/kubescape/opa-utils v0.0.267/go.mod h1:95JkuIOfClgLc+DyGb2mDvefRW0STkZe4L2z6AaZJlQ=
github.com/kubescape/rbac-utils v0.0.21-0.20230806101615-07e36f555520 h1:SqlwF8G+oFazeYmZQKoPczLEflBQpwpHCU8DoLLyfj8=
github.com/kubescape/rbac-utils v0.0.21-0.20230806101615-07e36f555520/go.mod h1:wuxMUSDzGUyWd25IJfBzEJ/Udmw2Vy7npj+MV3u3GrU=
github.com/kubescape/regolibrary v1.0.291-rc.0 h1:DztPS3NSKfiltO1wZvxRjuu5c99c6+dEgfTs6DcsVa8=