core(cmd): adding corrections to cmd (#1357)

* adding corrections to cmd

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

* remove decorative line

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

* wip: changed results indicator

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

* replace status test with icons

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

* print workloads in a different line

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

* update display

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

* deprecate commands

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

* removed unused functions

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

* fixed tests

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

* update cloud provider detection

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

* rename column name

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

---------

Signed-off-by: David Wertenteil <dwertent@armosec.io>
This commit is contained in:
David Wertenteil
2023-08-29 09:50:22 +03:00
committed by GitHub
parent 8d1547163b
commit 92449bf564
33 changed files with 412 additions and 637 deletions

View File

@@ -1,28 +0,0 @@
package delete
import (
"github.com/kubescape/kubescape/v2/core/meta"
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/spf13/cobra"
)
func GetDeleteCmd(ks meta.IKubescape) *cobra.Command {
var deleteInfo v1.Delete
var deleteCmd = &cobra.Command{
Use: "delete <command>",
Short: "Delete configurations in Kubescape SaaS version",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
},
}
deleteCmd.PersistentFlags().StringVarP(&deleteInfo.AccountID, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
deleteCmd.PersistentFlags().MarkDeprecated("client-id", "Client ID is no longer supported. Feel free to contact the Kubescape maintainers for more information.")
deleteCmd.PersistentFlags().MarkDeprecated("secret-key", "Secret Key is no longer supported. Feel free to contact the Kubescape maintainers for more information.")
deleteCmd.AddCommand(&cobra.Command{
Use: "exceptions",
Deprecated: "Contact Kubescape maintainers for more information.",
})
return deleteCmd
}

View File

@@ -27,7 +27,7 @@ func GetFixCmd(ks meta.IKubescape) *cobra.Command {
fixCmd := &cobra.Command{
Use: "fix <report output file>",
Short: "Fix misconfiguration in files",
Short: "Propose a fix for the misconfiguration found when scanning Kubernetes manifest files",
Long: ``,
Example: fixCmdExamples,
RunE: func(cmd *cobra.Command, args []string) error {

View File

@@ -8,12 +8,10 @@ import (
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v2/cmd/completion"
"github.com/kubescape/kubescape/v2/cmd/config"
"github.com/kubescape/kubescape/v2/cmd/delete"
"github.com/kubescape/kubescape/v2/cmd/download"
"github.com/kubescape/kubescape/v2/cmd/fix"
"github.com/kubescape/kubescape/v2/cmd/list"
"github.com/kubescape/kubescape/v2/cmd/scan"
"github.com/kubescape/kubescape/v2/cmd/submit"
"github.com/kubescape/kubescape/v2/cmd/update"
"github.com/kubescape/kubescape/v2/cmd/version"
"github.com/kubescape/kubescape/v2/core/cautils"
@@ -27,11 +25,11 @@ import (
var rootInfo cautils.RootInfo
var ksExamples = fmt.Sprintf(`
# Scan command
# Scan a Kubernetes cluster or YAML files for image vulnerabilities and misconfigurations
%[1]s scan
# List supported frameworks
%[1]s list frameworks
# List supported controls
%[1]s list controls
# Download artifacts (air-gapped environment support)
%[1]s download artifacts
@@ -74,23 +72,31 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
rootCmd.PersistentFlags().StringVarP(&rootInfo.Logger, "logger", "l", helpers.InfoLevel.String(), fmt.Sprintf("Logger level. Supported: %s [$KS_LOGGER]", strings.Join(helpers.SupportedLevels(), "/")))
rootCmd.PersistentFlags().StringVar(&rootInfo.CacheDir, "cache-dir", getter.DefaultLocalStore, "Cache directory [$KS_CACHE_DIR]")
rootCmd.PersistentFlags().BoolVarP(&rootInfo.DisableColor, "disable-color", "", false, "Disable Color output for logging")
rootCmd.PersistentFlags().BoolVarP(&rootInfo.EnableColor, "enable-color", "", false, "Force enable Color output for logging")
rootCmd.PersistentFlags().BoolVarP(&rootInfo.DisableColor, "disable-color", "", false, "Disable color output for logging")
rootCmd.PersistentFlags().BoolVarP(&rootInfo.EnableColor, "enable-color", "", false, "Force enable color output for logging")
cobra.OnInitialize(initLogger, initLoggerLevel, initEnvironment, initCacheDir)
// Supported commands
rootCmd.AddCommand(scan.GetScanCommand(ks))
rootCmd.AddCommand(download.GetDownloadCmd(ks))
rootCmd.AddCommand(delete.GetDeleteCmd(ks))
rootCmd.AddCommand(list.GetListCmd(ks))
rootCmd.AddCommand(submit.GetSubmitCmd(ks))
rootCmd.AddCommand(completion.GetCompletionCmd())
rootCmd.AddCommand(version.GetVersionCmd())
rootCmd.AddCommand(config.GetConfigCmd(ks))
rootCmd.AddCommand(update.GetUpdateCmd())
rootCmd.AddCommand(fix.GetFixCmd(ks))
// deprecated commands
rootCmd.AddCommand(&cobra.Command{
Use: "submit",
Deprecated: "This command is deprecated. Contact Kubescape maintainers for more information.",
})
rootCmd.AddCommand(&cobra.Command{
Use: "delete",
Deprecated: "This command is deprecated. Contact Kubescape maintainers for more information.",
})
return rootCmd
}

View File

@@ -17,14 +17,14 @@ import (
var scanCmdExamples = fmt.Sprintf(`
Scan command is for scanning an existing cluster or kubernetes manifest files based on pre-defined frameworks
# Scan current cluster with all frameworks
# Scan current cluster
%[1]s scan
# Scan kubernetes YAML manifest files
# Scan kubernetes manifest files
%[1]s scan .
# Scan and save the results in the JSON format
%[1]s scan --format json --output results.json --format-version=v2
%[1]s scan --format json --output results.json
# Display all resources
%[1]s scan --verbose
@@ -39,7 +39,7 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
// scanCmd represents the scan command
scanCmd := &cobra.Command{
Use: "scan",
Short: "Scan the current running cluster or yaml files",
Short: "Scan a Kubernetes cluster or YAML files for image vulnerabilities and misconfigurations",
Long: `The action you want to perform`,
Example: scanCmdExamples,
Args: func(cmd *cobra.Command, args []string) error {

View File

@@ -1,124 +0,0 @@
package submit
import (
"context"
"encoding/json"
"fmt"
"os"
"github.com/google/uuid"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/cautils/getter"
reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/k8s-interface/k8sinterface"
"github.com/kubescape/k8s-interface/workloadinterface"
"github.com/kubescape/kubescape/v2/core/meta"
"github.com/kubescape/kubescape/v2/core/meta/cliinterfaces"
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
reporterv2 "github.com/kubescape/kubescape/v2/core/pkg/resultshandling/reporter/v2"
"github.com/spf13/cobra"
)
var formatVersion string
type ResultsObject struct {
filePath string
customerGUID string
clusterName string
}
func NewResultsObject(customerGUID, clusterName, filePath string) *ResultsObject {
return &ResultsObject{
filePath: filePath,
customerGUID: customerGUID,
clusterName: clusterName,
}
}
func (resultsObject *ResultsObject) SetResourcesReport() (*reporthandlingv2.PostureReport, error) {
// load framework results from json file
report, err := loadResultsFromFile(resultsObject.filePath)
if err != nil {
return nil, err
}
return report, nil
}
func (resultsObject *ResultsObject) ListAllResources() (map[string]workloadinterface.IMetadata, error) {
return map[string]workloadinterface.IMetadata{}, nil
}
func getResultsCmd(ks meta.IKubescape, submitInfo *v1.Submit) *cobra.Command {
var resultsCmd = &cobra.Command{
Use: fmt.Sprintf("results <json file>\nExample:\n$ %[1]s submit results path/to/results.json --format-version v2", cautils.ExecName()),
Short: "Submit a pre scanned results file. The file must be in json format",
Long: ``,
RunE: func(cmd *cobra.Command, args []string) error {
if err := flagValidationSubmit(submitInfo); err != nil {
return err
}
if len(args) == 0 {
return fmt.Errorf("missing results file")
}
k8s := getKubernetesApi()
// get config
clusterConfig := getTenantConfig(submitInfo.AccountID, "", "", k8s)
resultsObjects := NewResultsObject(clusterConfig.GetAccountID(), clusterConfig.GetContextName(), args[0])
r := reporterv2.NewReportEventReceiver(clusterConfig, uuid.NewString(), reporterv2.SubmitContextScan)
submitInterfaces := cliinterfaces.SubmitInterfaces{
ClusterConfig: clusterConfig,
SubmitObjects: resultsObjects,
Reporter: r,
}
if err := ks.Submit(context.TODO(), submitInterfaces); err != nil {
logger.L().Fatal(err.Error())
}
return nil
},
}
resultsCmd.PersistentFlags().StringVar(&formatVersion, "format-version", "v2", "Output object can be different between versions, this is for maintaining backward and forward compatibility. Supported:'v1'/'v2'")
return resultsCmd
}
func loadResultsFromFile(filePath string) (*reporthandlingv2.PostureReport, error) {
report := &reporthandlingv2.PostureReport{}
f, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}
if err = json.Unmarshal(f, report); err != nil {
return report, fmt.Errorf("failed to unmarshal results file: %s, make sure you run kubescape with '--format=json --format-version=v2'", err.Error())
}
return report, nil
}
// getKubernetesApi
func getKubernetesApi() *k8sinterface.KubernetesApi {
if !k8sinterface.IsConnectedToCluster() {
return nil
}
return k8sinterface.NewKubernetesApi()
}
func getTenantConfig(accountID, clusterName string, customClusterName string, k8s *k8sinterface.KubernetesApi) cautils.ITenantConfig {
if !k8sinterface.IsConnectedToCluster() || k8s == nil {
return cautils.NewLocalConfig(getter.GetKSCloudAPIConnector(), accountID, clusterName, customClusterName)
}
return cautils.NewClusterConfig(k8s, getter.GetKSCloudAPIConnector(), accountID, clusterName, customClusterName)
}
// Check if the flag entered are valid
func flagValidationSubmit(submitInfo *v1.Submit) error {
// Validate the user's credentials
return cautils.ValidateAccountID(submitInfo.AccountID)
}

View File

@@ -1,45 +0,0 @@
package submit
import (
"fmt"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/meta"
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/spf13/cobra"
)
var submitCmdExamples = fmt.Sprintf(`
# Submit Kubescape scan results file
%[1]s submit results
`, cautils.ExecName())
func GetSubmitCmd(ks meta.IKubescape) *cobra.Command {
var submitInfo metav1.Submit
submitCmd := &cobra.Command{
Use: "submit <command>",
Short: "Submit an object to the Kubescape SaaS version",
Long: ``,
Example: submitCmdExamples,
Run: func(cmd *cobra.Command, args []string) {
},
}
submitCmd.PersistentFlags().StringVarP(&submitInfo.AccountID, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
submitCmd.PersistentFlags().MarkDeprecated("client-id", "Client ID is no longer supported. Feel free to contact the Kubescape maintainers for more information.")
submitCmd.PersistentFlags().MarkDeprecated("secret-key", "Secret Key is no longer supported. Feel free to contact the Kubescape maintainers for more information.")
submitCmd.AddCommand(getResultsCmd(ks, &submitInfo))
// deprecated commands
submitCmd.AddCommand(&cobra.Command{
Use: "exceptions",
Deprecated: "Contact Kubescape maintainers for more information.",
})
submitCmd.AddCommand(&cobra.Command{
Use: "rbac",
Deprecated: "Contact Kubescape maintainers for more information.",
})
return submitCmd
}

View File

@@ -8,6 +8,7 @@ import (
"fmt"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/spf13/cobra"
)
@@ -24,16 +25,16 @@ var updateCmdExamples = fmt.Sprintf(`
func GetUpdateCmd() *cobra.Command {
updateCmd := &cobra.Command{
Use: "update",
Short: "Update your version",
Short: "Update to latest release version",
Long: ``,
Example: updateCmdExamples,
RunE: func(_ *cobra.Command, args []string) error {
//Checking the user's version of kubescape to the latest release
if cautils.BuildNumber == cautils.LatestReleaseVersion {
//your version == latest version
logger.L().Info(("You are in the latest version"))
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", installationLink)
fmt.Printf("Please refer to our installation docs in the following link: %s", installationLink)
}
return nil
},

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"os"
"github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/spf13/cobra"
)
@@ -19,10 +20,10 @@ func GetVersionCmd() *cobra.Command {
v := cautils.NewIVersionCheckHandler(ctx)
v.CheckLatestVersion(ctx, cautils.NewVersionCheckRequest(cautils.BuildNumber, "", "", "version"))
fmt.Fprintf(os.Stdout,
"Your current version is: %s [git enabled in build: %t]\n",
"Your current version is: %s\n",
cautils.BuildNumber,
isGitEnabled(),
)
logger.L().Debug(fmt.Sprintf("git enabled in build: %t", isGitEnabled()))
return nil
},
}

View File

@@ -5,7 +5,6 @@ import (
"github.com/armosec/utils-go/boolutils"
cloudsupport "github.com/kubescape/k8s-interface/cloudsupport/v1"
"github.com/kubescape/k8s-interface/k8sinterface"
"github.com/kubescape/opa-utils/reporthandling"
"github.com/kubescape/opa-utils/reporthandling/apis"
)
@@ -89,58 +88,50 @@ func isRuleKubescapeVersionCompatible(attributes map[string]interface{}, version
return true
}
func getCloudType(scanInfo *ScanInfo) (bool, reporthandling.ScanningScopeType) {
func getCloudProvider(scanInfo *ScanInfo) reporthandling.ScanningScopeType {
if cloudsupport.IsAKS() {
return true, reporthandling.ScopeCloudAKS
return reporthandling.ScopeCloudAKS
}
if cloudsupport.IsEKS(k8sinterface.GetConfig()) {
return true, reporthandling.ScopeCloudEKS
if cloudsupport.IsEKS() {
return reporthandling.ScopeCloudEKS
}
if cloudsupport.IsGKE(k8sinterface.GetConfig()) {
return true, reporthandling.ScopeCloudGKE
if cloudsupport.IsGKE() {
return reporthandling.ScopeCloudGKE
}
return false, ""
return ""
}
func GetScanningScope(scanInfo *ScanInfo) reporthandling.ScanningScopeType {
var result reporthandling.ScanningScopeType
switch scanInfo.GetScanningContext() {
case ContextCluster:
isCloud, cloudType := getCloudType(scanInfo)
if isCloud {
result = cloudType
} else {
result = reporthandling.ScopeCluster
if cloudProvider := getCloudProvider(scanInfo); cloudProvider != "" {
return cloudProvider
}
return reporthandling.ScopeCluster
default:
result = reporthandling.ScopeFile
return reporthandling.ScopeFile
}
return result
}
func isScanningScopeMatchToControlScope(scanScope reporthandling.ScanningScopeType, controlScope reporthandling.ScanningScopeType) bool {
result := false
switch controlScope {
case reporthandling.ScopeFile:
result = (reporthandling.ScopeFile == scanScope)
return reporthandling.ScopeFile == scanScope
case reporthandling.ScopeCluster:
result = (reporthandling.ScopeCluster == scanScope) || (reporthandling.ScopeCloud == scanScope) || (reporthandling.ScopeCloudAKS == scanScope) || (reporthandling.ScopeCloudEKS == scanScope) || (reporthandling.ScopeCloudGKE == scanScope)
return reporthandling.ScopeCluster == scanScope || reporthandling.ScopeCloud == scanScope || reporthandling.ScopeCloudAKS == scanScope || reporthandling.ScopeCloudEKS == scanScope || reporthandling.ScopeCloudGKE == scanScope
case reporthandling.ScopeCloud:
result = (reporthandling.ScopeCloud == scanScope) || (reporthandling.ScopeCloudAKS == scanScope) || (reporthandling.ScopeCloudEKS == scanScope) || (reporthandling.ScopeCloudGKE == scanScope)
return reporthandling.ScopeCloud == scanScope || reporthandling.ScopeCloudAKS == scanScope || reporthandling.ScopeCloudEKS == scanScope || reporthandling.ScopeCloudGKE == scanScope
case reporthandling.ScopeCloudAKS:
result = (reporthandling.ScopeCloudAKS == scanScope)
return reporthandling.ScopeCloudAKS == scanScope
case reporthandling.ScopeCloudEKS:
result = (reporthandling.ScopeCloudEKS == scanScope)
return reporthandling.ScopeCloudEKS == scanScope
case reporthandling.ScopeCloudGKE:
result = (reporthandling.ScopeCloudGKE == scanScope)
return reporthandling.ScopeCloudGKE == scanScope
default:
result = true
return true
}
return result
}
func isControlFitToScanScope(control reporthandling.Control, scanScopeMatches reporthandling.ScanningScopeType) bool {

View File

@@ -4,8 +4,8 @@ import (
"fmt"
"testing"
"github.com/armosec/armoapi-go/armotypes"
"github.com/kubescape/opa-utils/reporthandling"
"github.com/stretchr/testify/assert"
)
@@ -79,7 +79,6 @@ func TestIsControlFitToScanScope(t *testing.T) {
scanInfo: &ScanInfo{},
Control: reporthandling.Control{
ScanningScope: &reporthandling.ScanningScope{
Matches: []reporthandling.ScanningScopeType{
reporthandling.ScopeCloudEKS,
},
@@ -99,6 +98,237 @@ func TestIsControlFitToScanScope(t *testing.T) {
expected_res: false,
}}
for i := range tests {
assert.Equal(t, isControlFitToScanScope(tests[i].Control, GetScanningScope(tests[i].scanInfo)), tests[i].expected_res, fmt.Sprintf("tests_true index %d", i))
assert.Equal(t, tests[i].expected_res, isControlFitToScanScope(tests[i].Control, GetScanningScope(tests[i].scanInfo)), fmt.Sprintf("tests_true index %d", i))
}
}
func TestIsScanningScopeMatchToControlScope(t *testing.T) {
tests := []struct {
scanScope reporthandling.ScanningScopeType
controlScope reporthandling.ScanningScopeType
expected bool
}{
{
scanScope: reporthandling.ScopeFile,
controlScope: reporthandling.ScopeFile,
expected: true,
},
{
scanScope: ScopeCluster,
controlScope: ScopeCluster,
expected: true,
},
{
scanScope: reporthandling.ScopeCloud,
controlScope: reporthandling.ScopeCloud,
expected: true,
},
{
scanScope: reporthandling.ScopeCloudAKS,
controlScope: reporthandling.ScopeCloudAKS,
expected: true,
},
{
scanScope: reporthandling.ScopeCloudEKS,
controlScope: reporthandling.ScopeCloudEKS,
expected: true,
},
{
scanScope: reporthandling.ScopeCloudGKE,
controlScope: reporthandling.ScopeCloudGKE,
expected: true,
},
{
scanScope: ScopeCluster,
controlScope: reporthandling.ScopeCloud,
expected: false,
},
{
scanScope: reporthandling.ScopeCloud,
controlScope: ScopeCluster,
expected: true,
},
{
scanScope: reporthandling.ScopeCloudAKS,
controlScope: ScopeCluster,
expected: true,
},
{
scanScope: reporthandling.ScopeCloudEKS,
controlScope: ScopeCluster,
expected: true,
},
{
scanScope: reporthandling.ScopeCloudGKE,
controlScope: ScopeCluster,
expected: true,
},
{
scanScope: reporthandling.ScopeCloud,
controlScope: reporthandling.ScopeCloudAKS,
expected: false,
},
{
scanScope: reporthandling.ScopeCloudAKS,
controlScope: reporthandling.ScopeCloud,
expected: true,
},
{
scanScope: reporthandling.ScopeCloudEKS,
controlScope: reporthandling.ScopeCloud,
expected: true,
},
{
scanScope: reporthandling.ScopeCloudGKE,
controlScope: reporthandling.ScopeCloud,
expected: true,
},
{
scanScope: ScopeCluster,
controlScope: reporthandling.ScopeCloudAKS,
expected: false,
},
{
scanScope: ScopeCluster,
controlScope: reporthandling.ScopeCloudEKS,
expected: false,
},
{
scanScope: ScopeCluster,
controlScope: reporthandling.ScopeCloudGKE,
expected: false,
},
{
scanScope: reporthandling.ScopeFile,
controlScope: ScopeCluster,
expected: false,
},
{
scanScope: reporthandling.ScopeFile,
controlScope: reporthandling.ScopeCloud,
expected: false,
},
{
scanScope: reporthandling.ScopeFile,
controlScope: reporthandling.ScopeCloudAKS,
expected: false,
},
{
scanScope: reporthandling.ScopeFile,
controlScope: reporthandling.ScopeCloudEKS,
expected: false,
},
{
scanScope: reporthandling.ScopeFile,
controlScope: reporthandling.ScopeCloudGKE,
expected: false,
},
{
scanScope: reporthandling.ScopeCloud,
controlScope: reporthandling.ScopeCloudEKS,
expected: false,
},
{
scanScope: reporthandling.ScopeCloud,
controlScope: reporthandling.ScopeCloudGKE,
expected: false,
},
{
scanScope: reporthandling.ScopeCloudAKS,
controlScope: reporthandling.ScopeCloudEKS,
expected: false,
},
{
scanScope: reporthandling.ScopeCloudAKS,
controlScope: reporthandling.ScopeCloudGKE,
expected: false,
},
{
scanScope: reporthandling.ScopeCloudEKS,
controlScope: reporthandling.ScopeCloudAKS,
expected: false,
},
{
scanScope: reporthandling.ScopeCloudEKS,
controlScope: reporthandling.ScopeCloudGKE,
expected: false,
},
{
scanScope: reporthandling.ScopeCloudGKE,
controlScope: reporthandling.ScopeCloudAKS,
expected: false,
},
{
scanScope: reporthandling.ScopeCloudGKE,
controlScope: reporthandling.ScopeCloudEKS,
expected: false,
},
}
for _, test := range tests {
result := isScanningScopeMatchToControlScope(test.scanScope, test.controlScope)
assert.Equal(t, test.expected, result, fmt.Sprintf("scanScope: %v, controlScope: %v", test.scanScope, test.controlScope))
}
}
func TestIsFrameworkFitToScanScope(t *testing.T) {
tests := []struct {
name string
framework reporthandling.Framework
scanScopeMatch reporthandling.ScanningScopeType
want bool
}{
{
name: "Framework with nil ScanningScope should return true",
framework: reporthandling.Framework{
PortalBase: armotypes.PortalBase{
Name: "test-framework",
},
},
scanScopeMatch: reporthandling.ScopeFile,
want: true,
},
{
name: "Framework with empty ScanningScope.Matches should return true",
framework: reporthandling.Framework{
PortalBase: armotypes.PortalBase{
Name: "test-framework",
}, ScanningScope: &reporthandling.ScanningScope{},
},
scanScopeMatch: reporthandling.ScopeFile,
want: true,
},
{
name: "Framework with matching ScanningScope.Matches should return true",
framework: reporthandling.Framework{
PortalBase: armotypes.PortalBase{
Name: "test-framework",
}, ScanningScope: &reporthandling.ScanningScope{
Matches: []reporthandling.ScanningScopeType{reporthandling.ScopeFile},
},
},
scanScopeMatch: reporthandling.ScopeFile,
want: true,
},
{
name: "Framework with non-matching ScanningScope.Matches should return false",
framework: reporthandling.Framework{
PortalBase: armotypes.PortalBase{
Name: "test-framework",
}, ScanningScope: &reporthandling.ScanningScope{
Matches: []reporthandling.ScanningScopeType{reporthandling.ScopeCluster},
},
},
scanScopeMatch: reporthandling.ScopeFile,
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := isFrameworkFitToScanScope(tt.framework, tt.scanScopeMatch); got != tt.want {
t.Errorf("isFrameworkFitToScanScope() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -296,11 +296,10 @@ func scanInfoToScanMetadata(ctx context.Context, scanInfo *ScanInfo) *reporthand
}
func (scanInfo *ScanInfo) GetScanningContext() ScanningContext {
input := ""
if len(scanInfo.InputPatterns) > 0 {
input = scanInfo.InputPatterns[0]
return GetScanningContext(scanInfo.InputPatterns[0])
}
return GetScanningContext(input)
return GetScanningContext("")
}
// GetScanningContext get scanning context from the input param

View File

@@ -2,6 +2,8 @@ package cautils
import (
"context"
"os"
"path/filepath"
"testing"
reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2"
@@ -37,12 +39,18 @@ func TestSetContextMetadata(t *testing.T) {
}
func TestGetHostname(t *testing.T) {
// Test that the hostname is not empty
assert.NotEqual(t, "", getHostname())
}
func TestGetScanningContext(t *testing.T) {
// Test with empty input
assert.Equal(t, ContextCluster, GetScanningContext(""))
// Test with Git URL input
assert.Equal(t, ContextGitURL, GetScanningContext("https://github.com/kubescape/kubescape"))
// TODO: Add more tests with other input types
}
func TestScanInfoFormats(t *testing.T) {
@@ -71,3 +79,30 @@ func TestScanInfoFormats(t *testing.T) {
})
}
}
func TestGetScanningContextWithFile(t *testing.T) {
// Test with a file
dir, err := os.MkdirTemp("", "example")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
filePath := filepath.Join(dir, "file.txt")
if _, err := os.Create(filePath); err != nil {
t.Fatal(err)
}
assert.Equal(t, ContextFile, GetScanningContext(filePath))
}
func TestGetScanningContextWithDir(t *testing.T) {
// Test with a directory
dir, err := os.MkdirTemp("", "example")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
assert.Equal(t, ContextDir, GetScanningContext(dir))
}

View File

@@ -1,36 +0,0 @@
package core
import (
"context"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/meta/cliinterfaces"
logger "github.com/kubescape/go-logger"
)
func (ks *Kubescape) Submit(ctx context.Context, submitInterfaces cliinterfaces.SubmitInterfaces) error {
// list resources
report, err := submitInterfaces.SubmitObjects.SetResourcesReport()
if err != nil {
return err
}
allresources, err := submitInterfaces.SubmitObjects.ListAllResources()
if err != nil {
return err
}
// report
o := &cautils.OPASessionObj{
Report: report,
AllResources: allresources,
Metadata: &report.Metadata,
}
if err := submitInterfaces.Reporter.Submit(ctx, o); err != nil {
return err
}
logger.L().Success("Data has been submitted successfully")
submitInterfaces.Reporter.DisplayMessage()
return nil
}

View File

@@ -4,7 +4,6 @@ import (
"context"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/meta/cliinterfaces"
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling"
)
@@ -16,9 +15,6 @@ type IKubescape interface {
List(ctx context.Context, listPolicies *metav1.ListPolicies) error // TODO - return list response
Download(ctx context.Context, downloadInfo *metav1.DownloadInfo) error // TODO - return downloaded policies
// submit
Submit(ctx context.Context, submitInterfaces cliinterfaces.SubmitInterfaces) error // TODO - func should receive object
// config
SetCachedConfig(setConfig *metav1.SetConfig) error
ViewCachedConfig(viewConfig *metav1.ViewConfig) error

View File

@@ -11,13 +11,13 @@ import (
"github.com/kubescape/opa-utils/reporthandling/apis"
helpersv1 "github.com/kubescape/opa-utils/reporthandling/helpers/v1"
reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2"
reportv2 "github.com/kubescape/opa-utils/reporthandling/v2"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/version"
"k8s.io/client-go/dynamic/fake"
fakeclientset "k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/rest"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)
@@ -49,7 +49,7 @@ func Test_getCloudMetadata(t *testing.T) {
name: "Test_getCloudMetadata - GitVersion: GKE",
args: args{
opaSessionObj: &cautils.OPASessionObj{
Report: &reporthandlingv2.PostureReport{
Report: &reportv2.PostureReport{
ClusterAPIServerInfo: &version.Info{
GitVersion: "v1.25.4-gke.1600",
},
@@ -60,25 +60,14 @@ func Test_getCloudMetadata(t *testing.T) {
},
want: helpersv1.NewGKEMetadata(""),
},
{
name: "Test_getCloudMetadata_context_GKE",
args: args{
opaSessionObj: &cautils.OPASessionObj{
Report: &reporthandlingv2.PostureReport{
ClusterAPIServerInfo: nil,
},
},
kubeConfig: kubeConfig,
context: "gke_xxx-xx-0000_us-central1-c_xxxx-1",
},
want: helpersv1.NewGKEMetadata(""),
},
{
name: "Test_getCloudMetadata_context_EKS",
args: args{
opaSessionObj: &cautils.OPASessionObj{
Report: &reporthandlingv2.PostureReport{
ClusterAPIServerInfo: nil,
Report: &reportv2.PostureReport{
ClusterAPIServerInfo: &version.Info{
GitVersion: "v1.25.4-eks.1600",
},
},
},
kubeConfig: kubeConfig,
@@ -90,8 +79,10 @@ func Test_getCloudMetadata(t *testing.T) {
name: "Test_getCloudMetadata_context_AKS",
args: args{
opaSessionObj: &cautils.OPASessionObj{
Report: &reporthandlingv2.PostureReport{
ClusterAPIServerInfo: nil,
Report: &reportv2.PostureReport{
ClusterAPIServerInfo: &version.Info{
GitVersion: "v1",
},
},
},
kubeConfig: kubeConfig,
@@ -100,10 +91,15 @@ func Test_getCloudMetadata(t *testing.T) {
want: helpersv1.NewAKSMetadata(""),
},
}
k8sinterface.K8SConfig = &rest.Config{}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
k8sinterface.SetClusterContextName(tt.args.context)
got := getCloudMetadata(tt.args.opaSessionObj, tt.args.kubeConfig)
k8sinterface.SetClientConfigAPI(tt.args.kubeConfig)
k8sinterface.SetK8SGitServerVersion(tt.args.opaSessionObj.Report.ClusterAPIServerInfo.GitVersion)
k8sinterface.SetConnectedToCluster(true)
got := getCloudMetadata(tt.args.opaSessionObj)
if got == nil {
t.Errorf("getCloudMetadata() = %v, want %v", got, tt.want.Provider())
return
@@ -113,112 +109,10 @@ func Test_getCloudMetadata(t *testing.T) {
}
})
}
k8sinterface.SetClusterContextName("")
k8sinterface.SetClientConfigAPI(nil)
}
func Test_isGKE(t *testing.T) {
type args struct {
config *clientcmdapi.Config
context string
}
tests := []struct {
name string
args args
want bool
}{
{
name: "Test_isGKE",
args: args{
config: getKubeConfigMock(),
context: "gke_xxx-xx-0000_us-central1-c_xxxx-1",
},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// set context
k8sinterface.SetClusterContextName(tt.args.context)
if got := isGKE(tt.args.config); got != tt.want {
t.Errorf("isGKE() = %v, want %v", got, tt.want)
}
})
}
}
func Test_isEKS(t *testing.T) {
type args struct {
config *clientcmdapi.Config
context string
}
tests := []struct {
name string
args args
want bool
}{
{
name: "Test_isEKS",
args: args{
config: getKubeConfigMock(),
context: "arn:aws:eks:eu-west-1:xxx:cluster/xxxx",
},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// set context
k8sinterface.SetClusterContextName(tt.args.context)
if got := isEKS(tt.args.config); got != tt.want {
t.Errorf("isEKS() = %v, want %v", got, tt.want)
}
})
}
}
func Test_isAKS(t *testing.T) {
type args struct {
config *clientcmdapi.Config
context string
}
tests := []struct {
name string
args args
want bool
}{
{
name: "Test_isAKS",
args: args{
config: getKubeConfigMock(),
context: "xxxx-2",
},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// set context
k8sinterface.SetClusterContextName(tt.args.context)
if got := isAKS(tt.args.config); got != tt.want {
t.Errorf("isAKS() = %v, want %v", got, tt.want)
}
})
}
}
/* unused for now.
type iResourceHandlerMock struct{}
func (*iResourceHandlerMock) GetResources(*cautils.OPASessionObj, *identifiers.PortalDesignator) (*cautils.K8SResources, map[string]workloadinterface.IMetadata, *cautils.KSResources, error) {
return nil, nil, nil, nil
}
func (*iResourceHandlerMock) GetClusterAPIServerInfo() *version.Info {
return nil
}
*/
// https://github.com/kubescape/kubescape/pull/1004
// Cluster named .*eks.* config without a cloudconfig panics whereas we just want to scan a file
func getResourceHandlerMock() *K8sResourceHandler {
@@ -237,12 +131,12 @@ func getResourceHandlerMock() *K8sResourceHandler {
func Test_CollectResources(t *testing.T) {
resourceHandler := getResourceHandlerMock()
objSession := &cautils.OPASessionObj{
Metadata: &reporthandlingv2.Metadata{
ScanMetadata: reporthandlingv2.ScanMetadata{
Metadata: &reportv2.Metadata{
ScanMetadata: reportv2.ScanMetadata{
ScanningTarget: reportv2.Cluster,
},
},
Report: &reporthandlingv2.PostureReport{
Report: &reportv2.PostureReport{
ClusterAPIServerInfo: nil,
},
}

View File

@@ -3,11 +3,9 @@ package resourcehandler
import (
"context"
"fmt"
"strings"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/k8s-interface/cloudsupport"
cloudsupportv1 "github.com/kubescape/k8s-interface/cloudsupport/v1"
"github.com/kubescape/k8s-interface/k8sinterface"
"github.com/kubescape/kubescape/v2/core/cautils"
@@ -16,7 +14,6 @@ import (
helpersv1 "github.com/kubescape/opa-utils/reporthandling/helpers/v1"
reportv2 "github.com/kubescape/opa-utils/reporthandling/v2"
"go.opentelemetry.io/otel"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)
func CollectResources(ctx context.Context, rsrcHandler IResourceHandler, policyIdentifier []cautils.PolicyIdentifier, opaSessionObj *cautils.OPASessionObj, progressListener opaprocessor.IJobProgressNotificationClient, scanInfo *cautils.ScanInfo) error {
@@ -47,7 +44,7 @@ func CollectResources(ctx context.Context, rsrcHandler IResourceHandler, policyI
}
func setCloudMetadata(opaSessionObj *cautils.OPASessionObj) {
iCloudMetadata := getCloudMetadata(opaSessionObj, k8sinterface.GetConfig())
iCloudMetadata := getCloudMetadata(opaSessionObj)
if iCloudMetadata == nil {
return
}
@@ -67,53 +64,15 @@ func setCloudMetadata(opaSessionObj *cautils.OPASessionObj) {
// 1. Get cloud provider from API server git version (EKS, GKE)
// 2. Get cloud provider from kubeconfig by parsing the cluster context (EKS, GKE)
// 3. Get cloud provider from kubeconfig by parsing the server URL (AKS)
func getCloudMetadata(opaSessionObj *cautils.OPASessionObj, config *clientcmdapi.Config) apis.ICloudParser {
if config == nil {
func getCloudMetadata(opaSessionObj *cautils.OPASessionObj) apis.ICloudParser {
switch cloudsupportv1.GetCloudProvider() {
case cloudsupportv1.AKS:
return helpersv1.NewAKSMetadata(k8sinterface.GetContextName())
case cloudsupportv1.EKS:
return helpersv1.NewEKSMetadata(k8sinterface.GetContextName())
case cloudsupportv1.GKE:
return helpersv1.NewGKEMetadata(k8sinterface.GetContextName())
default:
return nil
}
var provider string
// attempting to get cloud provider from API server git version
if opaSessionObj.Report.ClusterAPIServerInfo != nil {
provider = cloudsupport.GetCloudProvider(opaSessionObj.Report.ClusterAPIServerInfo.GitVersion)
}
if provider == cloudsupportv1.AKS || isAKS(config) {
return helpersv1.NewAKSMetadata(k8sinterface.GetContextName())
}
if provider == cloudsupportv1.EKS || isEKS(config) {
return helpersv1.NewEKSMetadata(k8sinterface.GetContextName())
}
if provider == cloudsupportv1.GKE || isGKE(config) {
return helpersv1.NewGKEMetadata(k8sinterface.GetContextName())
}
return nil
}
// check if the server is AKS. e.g. https://XXX.XX.XXX.azmk8s.io:443
func isAKS(config *clientcmdapi.Config) bool {
const serverIdentifierAKS = "azmk8s.io"
if cluster, ok := config.Clusters[k8sinterface.GetContextName()]; ok {
return strings.Contains(cluster.Server, serverIdentifierAKS)
}
return false
}
// check if the server is EKS. e.g. arn:aws:eks:eu-west-1:xxx:cluster/xxxx
func isEKS(config *clientcmdapi.Config) bool {
if context, ok := config.Contexts[k8sinterface.GetContextName()]; ok {
return strings.Contains(context.Cluster, cloudsupportv1.EKS)
}
return false
}
// check if the server is GKE. e.g. gke_xxx-xx-0000_us-central1-c_xxxx-1
func isGKE(config *clientcmdapi.Config) bool {
if context, ok := config.Contexts[k8sinterface.GetContextName()]; ok {
return strings.Contains(context.Cluster, cloudsupportv1.GKE)
}
return false
}

View File

@@ -189,7 +189,7 @@ func (k8sHandler *K8sResourceHandler) findScanObjectResource(resource *objectsen
}
func (k8sHandler *K8sResourceHandler) collectCloudResources(ctx context.Context, sessionObj *cautils.OPASessionObj, allResources map[string]workloadinterface.IMetadata, externalResourceMap cautils.ExternalResources, cloudResources []string, progressListener opaprocessor.IJobProgressNotificationClient) error {
provider := cloudsupport.GetCloudProvider(k8sHandler.clusterName)
provider := cloudsupport.GetCloudProvider()
if provider == "" {
return fmt.Errorf("failed to get cloud provider, cluster: %s", k8sHandler.clusterName)
}

View File

@@ -59,7 +59,7 @@ func (cp *ClusterPrinter) printTopWorkloads(topWorkloadsByScore []reporthandling
cautils.InfoTextDisplay(cp.writer, txt)
cautils.SimpleDisplay(cp.writer, fmt.Sprintf("%s\n\n", strings.Repeat("─", len(txt))))
cautils.SimpleDisplay(cp.writer, fmt.Sprintf("%s\n", strings.Repeat("─", len(txt))))
cautils.SimpleDisplay(cp.writer, highStakesWlsText)
@@ -67,7 +67,8 @@ func (cp *ClusterPrinter) printTopWorkloads(topWorkloadsByScore []reporthandling
ns := wl.GetNamespace()
name := wl.GetName()
kind := wl.GetKind()
cautils.SimpleDisplay(cp.writer, fmt.Sprintf("%d. namespace: %s, name: %s, kind: %s - '%s'\n", i+1, ns, name, kind, getCallToActionString(cp.getWorkloadScanCommand(ns, kind, name))))
cautils.SimpleDisplay(cp.writer, fmt.Sprintf("%d. namespace: %s, name: %s, kind: %s\n", i+1, ns, name, kind))
cautils.SimpleDisplay(cp.writer, fmt.Sprintf(" '%s'\n", getCallToActionString(cp.getWorkloadScanCommand(ns, kind, name))))
}
cautils.InfoTextDisplay(cp.writer, "\n")

View File

@@ -58,7 +58,7 @@ func (rp *RepoPrinter) printTopWorkloads(topWorkloadsByScore []reporthandling.IR
txt := getTopWorkloadsTitle(len(topWorkloadsByScore))
cautils.InfoTextDisplay(rp.writer, txt)
cautils.SimpleDisplay(rp.writer, fmt.Sprintf("%s\n\n", strings.Repeat("─", len(txt))))
cautils.SimpleDisplay(rp.writer, fmt.Sprintf("%s\n", strings.Repeat("─", len(txt))))
cautils.SimpleDisplay(rp.writer, highStakesWlsText)
@@ -67,7 +67,8 @@ func (rp *RepoPrinter) printTopWorkloads(topWorkloadsByScore []reporthandling.IR
name := wl.GetName()
kind := wl.GetKind()
cmdPrefix := getWorkloadPrefixForCmd(ns, kind, name)
cautils.SimpleDisplay(rp.writer, fmt.Sprintf("%d. %s - '%s'\n", i+1, cmdPrefix, getCallToActionString(rp.getWorkloadScanCommand(ns, kind, name, *wl.GetSource()))))
cautils.SimpleDisplay(rp.writer, fmt.Sprintf("%d. %s\n", i+1, cmdPrefix))
cautils.SimpleDisplay(rp.writer, fmt.Sprintf(" %s\n", getCallToActionString(rp.getWorkloadScanCommand(ns, kind, name, *wl.GetSource()))))
}
cautils.InfoTextDisplay(rp.writer, "\n")

View File

@@ -1,12 +1,10 @@
package configurationprinter
import (
"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/apis"
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
"github.com/olekukonko/tablewriter"
)
@@ -15,7 +13,7 @@ const (
docsPrefix = "https://hub.armosec.io/docs"
scanControlPrefix = "$ kubescape scan control"
controlNameHeader = "CONTROL NAME"
statusHeader = "STATUS"
statusHeader = ""
docsHeader = "DOCS"
resourcesHeader = "RESOURCES"
runHeader = "VIEW DETAILS"
@@ -31,8 +29,8 @@ func initCategoryTableData(categoryType CategoryType) ([]string, []int) {
func getCategoryStatusTypeHeaders() []string {
headers := make([]string, 3)
headers[0] = controlNameHeader
headers[1] = statusHeader
headers[0] = statusHeader
headers[1] = controlNameHeader
headers[2] = docsHeader
return headers
@@ -48,7 +46,7 @@ func getCategoryCountingTypeHeaders() []string {
}
func getStatusTypeAlignments() []int {
return []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER}
return []int{tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER}
}
func getCountingTypeAlignments() []int {
@@ -66,14 +64,14 @@ func generateCategoryStatusRow(controlSummary reportsummary.IControlSummary, inf
rows := make([]string, 3)
rows[0] = controlSummary.GetName()
if len(controlSummary.GetName()) > 50 {
rows[0] = controlSummary.GetName()[:50] + "..."
} else {
rows[0] = controlSummary.GetName()
}
rows[0] = utils.GetStatusIcon(controlSummary.GetStatus().Status())
rows[1] = utils.GetStatusColor(controlSummary.GetStatus().Status())(getStatus(status, controlSummary, infoToPrintInfo))
rows[1] = controlSummary.GetName()
if len(controlSummary.GetName()) > 50 {
rows[1] = controlSummary.GetName()[:50] + "..."
} else {
rows[1] = controlSummary.GetName()
}
rows[2] = getDocsForControl(controlSummary)
@@ -81,14 +79,6 @@ func generateCategoryStatusRow(controlSummary reportsummary.IControlSummary, inf
}
func getStatus(status apis.IStatus, controlSummary reportsummary.IControlSummary, infoToPrintInfo []utils.InfoStars) string {
// skipped is shown as action required
if status.IsSkipped() {
return fmt.Sprintf("%s %s", "action required", GetInfoColumn(controlSummary, infoToPrintInfo))
}
return string(controlSummary.GetStatus().Status())
}
func getCategoryTableWriter(writer io.Writer, headers []string, columnAligments []int) *tablewriter.Table {
table := tablewriter.NewWriter(writer)
table.SetHeader(headers)

View File

@@ -27,8 +27,8 @@ func TestInitCategoryTableData(t *testing.T) {
{
name: "Test2",
categoryType: TypeStatus,
expectedHeaders: []string{"CONTROL NAME", "STATUS", "DOCS"},
expectedAlignments: []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER},
expectedHeaders: []string{"", "CONTROL NAME", "DOCS"},
expectedAlignments: []int{tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER},
},
}
for _, tt := range tests {
@@ -53,12 +53,12 @@ func TestGetCategoryStatusTypeHeaders(t *testing.T) {
t.Errorf("Expected 3 headers, got %d", len(headers))
}
if headers[0] != controlNameHeader {
t.Errorf("Expected %s, got %s", controlNameHeader, headers[0])
if headers[0] != statusHeader {
t.Errorf("Expected %s, got %s", statusHeader, headers[0])
}
if headers[1] != statusHeader {
t.Errorf("Expected %s, got %s", statusHeader, headers[1])
if headers[1] != controlNameHeader {
t.Errorf("Expected %s, got %s", controlNameHeader, headers[1])
}
if headers[2] != docsHeader {
@@ -93,12 +93,12 @@ func TestGetStatusTypeAlignments(t *testing.T) {
t.Errorf("Expected 3 alignments, got %d", len(alignments))
}
if alignments[0] != tablewriter.ALIGN_LEFT {
t.Errorf("Expected %d, got %d", tablewriter.ALIGN_LEFT, alignments[0])
if alignments[0] != tablewriter.ALIGN_CENTER {
t.Errorf("Expected %d, got %d", tablewriter.ALIGN_CENTER, alignments[0])
}
if alignments[1] != tablewriter.ALIGN_CENTER {
t.Errorf("Expected %d, got %d", tablewriter.ALIGN_CENTER, alignments[1])
if alignments[1] != tablewriter.ALIGN_LEFT {
t.Errorf("Expected %d, got %d", tablewriter.ALIGN_LEFT, alignments[1])
}
if alignments[2] != tablewriter.ALIGN_CENTER {
@@ -140,7 +140,7 @@ func TestGenerateCategoryStatusRow(t *testing.T) {
Status: apis.StatusFailed,
ControlID: "ctrlID",
},
expectedRows: []string{"test", "failed", "https://hub.armosec.io/docs/ctrlid"},
expectedRows: []string{"", "test", "https://hub.armosec.io/docs/ctrlid"},
},
{
name: "skipped control",
@@ -152,7 +152,7 @@ func TestGenerateCategoryStatusRow(t *testing.T) {
},
ControlID: "ctrlID",
},
expectedRows: []string{"test", "action required *", "https://hub.armosec.io/docs/ctrlid"},
expectedRows: []string{"⚠️", "test", "https://hub.armosec.io/docs/ctrlid"},
infoToPrintInfo: []utils.InfoStars{
{
Info: "testInfo",
@@ -167,7 +167,7 @@ func TestGenerateCategoryStatusRow(t *testing.T) {
Status: apis.StatusPassed,
ControlID: "ctrlID",
},
expectedRows: []string{"test", "passed", "https://hub.armosec.io/docs/ctrlid"},
expectedRows: []string{"", "test", "https://hub.armosec.io/docs/ctrlid"},
},
{
name: "big name",
@@ -176,7 +176,7 @@ func TestGenerateCategoryStatusRow(t *testing.T) {
Status: apis.StatusFailed,
ControlID: "ctrlID",
},
expectedRows: []string{"testtesttesttesttesttesttesttesttesttesttesttestte...", "failed", "https://hub.armosec.io/docs/ctrlid"},
expectedRows: []string{"❌", "testtesttesttesttesttesttesttesttesttesttesttestte...", "https://hub.armosec.io/docs/ctrlid"},
},
}

View File

@@ -60,16 +60,3 @@ func (wp *WorkloadPrinter) renderSingleCategoryTable(categoryName string, catego
func (wp *WorkloadPrinter) initCategoryTableData() ([]string, []int) {
return getCategoryStatusTypeHeaders(), getStatusTypeAlignments()
}
func (wp *WorkloadPrinter) generateCountingCategoryRow(controlSummary reportsummary.IControlSummary, infoToPrintInfo []utils.InfoStars) []string {
row := make([]string, 3)
row[0] = controlSummary.GetName()
row[1] = getStatus(controlSummary.GetStatus(), controlSummary, infoToPrintInfo)
row[2] = getDocsForControl(controlSummary)
return row
}

View File

@@ -1,20 +1,15 @@
package configurationprinter
import (
"reflect"
"testing"
"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"
"github.com/stretchr/testify/assert"
)
func TestWorkloadScan_InitCategoryTableData(t *testing.T) {
expectedHeader := []string{"CONTROL NAME", "STATUS", "DOCS"}
expectedAlign := []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER}
expectedHeader := []string{"", "CONTROL NAME", "DOCS"}
expectedAlign := []int{tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER}
workloadPrinter := NewWorkloadPrinter()
@@ -33,85 +28,3 @@ func TestWorkloadScan_InitCategoryTableData(t *testing.T) {
}
}
func TestWorkloadScan_GenerateCountingCategoryRow(t *testing.T) {
tests := []struct {
name string
controlSummary reportsummary.IControlSummary
infoToPrint []utils.InfoStars
expectedRows []string
}{
{
name: "1 failed control",
controlSummary: &reportsummary.ControlSummary{
StatusInfo: apis.StatusInfo{
InnerStatus: apis.StatusFailed,
},
ControlID: "ctrl1",
Name: "ctrl1",
StatusCounters: reportsummary.StatusCounters{
FailedResources: 1,
},
},
expectedRows: []string{"ctrl1", "failed", "https://hub.armosec.io/docs/ctrl1"},
},
{
name: "multiple failed controls",
controlSummary: &reportsummary.ControlSummary{
StatusInfo: apis.StatusInfo{
InnerStatus: apis.StatusFailed,
},
ControlID: "ctrl1",
Name: "ctrl1",
StatusCounters: reportsummary.StatusCounters{
FailedResources: 5,
},
},
expectedRows: []string{"ctrl1", "failed", "https://hub.armosec.io/docs/ctrl1"},
},
{
name: "no failed controls",
controlSummary: &reportsummary.ControlSummary{
StatusInfo: apis.StatusInfo{
InnerStatus: apis.StatusPassed,
},
ControlID: "ctrl1",
Name: "ctrl1",
StatusCounters: reportsummary.StatusCounters{
FailedResources: 0,
},
},
expectedRows: []string{"ctrl1", "passed", "https://hub.armosec.io/docs/ctrl1"},
},
{
name: "action required",
infoToPrint: []utils.InfoStars{
{
Info: "action required",
Stars: "*",
},
},
controlSummary: &reportsummary.ControlSummary{
ControlID: "ctrl1",
StatusInfo: apis.StatusInfo{
InnerStatus: apis.StatusSkipped,
InnerInfo: "action required",
},
Name: "ctrl1",
StatusCounters: reportsummary.StatusCounters{
SkippedResources: 1,
},
},
expectedRows: []string{"ctrl1", "action required *", "https://hub.armosec.io/docs/ctrl1"},
},
}
workloadPrinter := NewWorkloadPrinter()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
row := workloadPrinter.generateCountingCategoryRow(tt.controlSummary, tt.infoToPrint)
assert.True(t, reflect.DeepEqual(row, tt.expectedRows))
})
}
}

View File

@@ -64,7 +64,7 @@ func generateRow(cve CVE) []string {
func getImageScanningHeaders() []string {
headers := make([]string, 5)
headers[imageColumnSeverity] = "SEVERITY"
headers[imageColumnName] = "NAME"
headers[imageColumnName] = "VULNERABILITY"
headers[imageColumnComponent] = "COMPONENT"
headers[imageColumnVersion] = "VERSION"
headers[imageColumnFixedIn] = "FIXED IN"

View File

@@ -140,7 +140,7 @@ func TestGenerateRow(t *testing.T) {
func TestGetImageScanningHeaders(t *testing.T) {
headers := getImageScanningHeaders()
expectedHeaders := []string{"SEVERITY", "NAME", "COMPONENT", "VERSION", "FIXED IN"}
expectedHeaders := []string{"SEVERITY", "VULNERABILITY", "COMPONENT", "VERSION", "FIXED IN"}
for i := range headers {
if headers[i] != expectedHeaders[i] {

View File

@@ -125,18 +125,16 @@ func GetStatusColor(status apis.ScanningStatus) func(...string) string {
}
}
func getColor(controlSeverity int) func(...string) string {
switch controlSeverity {
case apis.SeverityCritical:
return gchalk.WithAnsi256(1).Bold
case apis.SeverityHigh:
return gchalk.WithAnsi256(196).Bold
case apis.SeverityMedium:
return gchalk.WithAnsi256(166).Bold
case apis.SeverityLow:
return gchalk.WithAnsi256(220).Bold
func GetStatusIcon(status apis.ScanningStatus) string {
switch status {
case apis.StatusPassed:
return "✅"
case apis.StatusFailed:
return "❌"
case apis.StatusSkipped:
return "⚠️"
default:
return gchalk.WithAnsi256(16).Bold
return "⚠️"
}
}

View File

@@ -203,11 +203,15 @@ func printImageScanningSummary(writer *os.File, summary imageprinter.ImageScanSu
})
if len(summary.CVEs) == 0 {
cautils.InfoTextDisplay(writer, "Vulnerability summary - no vulnerabilities were found!\n\n")
txt := "Vulnerability summary - no vulnerabilities were found!"
cautils.InfoTextDisplay(writer, txt+"\n")
cautils.SimpleDisplay(writer, strings.Repeat("─", len(txt))+"\n")
return
}
cautils.InfoTextDisplay(writer, "Vulnerability summary - %d vulnerabilities found:\n", len(summary.CVEs))
txt := fmt.Sprintf("Vulnerability summary - %d vulnerabilities found:", len(summary.CVEs))
cautils.InfoTextDisplay(writer, txt+"\n")
cautils.SimpleDisplay(writer, strings.Repeat("─", len(txt))+"\n")
if len(summary.Images) == 1 {
cautils.SimpleDisplay(writer, "Image: %s\n", summary.Images[0])
@@ -238,7 +242,7 @@ 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\n", strings.Repeat("─", len(txt))))
cautils.SimpleDisplay(writer, fmt.Sprintf("%s\n", strings.Repeat("─", len(txt))))
for _, ns := range nextSteps {
cautils.SimpleDisplay(writer, "* "+ns+"\n")
@@ -252,7 +256,7 @@ func printComplianceScore(writer *os.File, frameworks []reportsummary.IFramework
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, fmt.Sprintf("%s\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")
@@ -260,7 +264,7 @@ func printComplianceScore(writer *os.File, frameworks []reportsummary.IFramework
cautils.SimpleDisplay(writer, "* %s: %s", fw.GetName(), gchalk.WithYellow().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.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

@@ -14,6 +14,8 @@ import (
reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2"
)
const indicator = "†"
// finalizeV2Report finalize the results objects by copying data from map to lists
func FinalizeResults(data *cautils.OPASessionObj) *reporthandlingv2.PostureReport {
report := reporthandlingv2.PostureReport{
@@ -57,7 +59,7 @@ type infoStars struct {
func mapInfoToPrintInfo(controls reportsummary.ControlSummaries) []infoStars {
infoToPrintInfo := []infoStars{}
infoToPrintInfoMap := map[string]interface{}{}
starCount := "*"
starCount := indicator
for _, control := range controls {
if control.GetStatus().IsSkipped() && control.GetStatus().Info() != "" {
if _, ok := infoToPrintInfoMap[control.GetStatus().Info()]; !ok {
@@ -65,7 +67,7 @@ func mapInfoToPrintInfo(controls reportsummary.ControlSummaries) []infoStars {
info: control.GetStatus().Info(),
stars: starCount,
})
starCount += "*"
starCount += indicator
infoToPrintInfoMap[control.GetStatus().Info()] = nil
}
}

View File

@@ -259,7 +259,7 @@ func (report *ReportEventReceiver) sendReport(host string, postureReport *report
}
func (report *ReportEventReceiver) setMessage(message string) {
report.message = "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" + message
report.message = message
}
func (report *ReportEventReceiver) DisplayMessage() {
@@ -271,6 +271,6 @@ func (report *ReportEventReceiver) DisplayMessage() {
cautils.SimpleDisplay(os.Stderr, strings.Repeat("─", len(txt)))
cautils.SimpleDisplay(os.Stderr, fmt.Sprintf("\n\n%s\n\n", report.message))
cautils.SimpleDisplay(os.Stderr, fmt.Sprintf("\n%s\n\n", report.message))
}
}

2
go.mod
View File

@@ -23,7 +23,7 @@ require (
github.com/kubescape/backend v0.0.0-20230820141235-28748e7aad2a
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/k8s-interface v0.0.141
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

4
go.sum
View File

@@ -1292,8 +1292,8 @@ github.com/kubescape/go-git-url v0.0.25 h1:i7SSSC1+1m/Dg+4LV3erp0YklnWj1Z0cVlRxC
github.com/kubescape/go-git-url v0.0.25/go.mod h1:IbVT7Wsxlghsa+YxI5KOx4k9VQJaa3z0kTaQz5D3nKM=
github.com/kubescape/go-logger v0.0.20 h1:ZU3T6Za7maCiChdoTrqpD6TI11DGJwd9xU/TFtRlMOI=
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/k8s-interface v0.0.141 h1:CQcK3PZsSeDFVmlvyHya48NJihGe7Qc+3kEWsmc4KnA=
github.com/kubescape/k8s-interface v0.0.141/go.mod h1:5sz+5Cjvo98lTbTVDiDA4MmlXxeHSVMW/wR0V3hV4K8=
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=

View File

@@ -11,7 +11,7 @@ require (
github.com/gorilla/mux v1.8.0
github.com/gorilla/schema v1.2.0
github.com/kubescape/go-logger v0.0.20
github.com/kubescape/k8s-interface v0.0.138
github.com/kubescape/k8s-interface v0.0.141
github.com/kubescape/kubescape/v2 v2.0.0-00010101000000-000000000000
github.com/kubescape/opa-utils v0.0.267
github.com/stretchr/testify v1.8.4

View File

@@ -1296,8 +1296,8 @@ github.com/kubescape/go-git-url v0.0.25 h1:i7SSSC1+1m/Dg+4LV3erp0YklnWj1Z0cVlRxC
github.com/kubescape/go-git-url v0.0.25/go.mod h1:IbVT7Wsxlghsa+YxI5KOx4k9VQJaa3z0kTaQz5D3nKM=
github.com/kubescape/go-logger v0.0.20 h1:ZU3T6Za7maCiChdoTrqpD6TI11DGJwd9xU/TFtRlMOI=
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/k8s-interface v0.0.141 h1:CQcK3PZsSeDFVmlvyHya48NJihGe7Qc+3kEWsmc4KnA=
github.com/kubescape/k8s-interface v0.0.141/go.mod h1:5sz+5Cjvo98lTbTVDiDA4MmlXxeHSVMW/wR0V3hV4K8=
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=