Resolving conflicts

Signed-off-by: Amit Schendel <amitschendel@gmail.com>
This commit is contained in:
Amit Schendel
2024-03-03 16:25:32 +02:00
72 changed files with 3049 additions and 3635 deletions
+2 -2
View File
@@ -150,7 +150,7 @@ func (policyHandler *PolicyHandler) downloadScanPolicies(ctx context.Context, po
logger.L().Debug("Downloading framework", helpers.String("framework", rule.Identifier))
receivedFramework, err := policyHandler.getters.PolicyGetter.GetFramework(rule.Identifier)
if err != nil {
return frameworks, policyDownloadError(err)
return frameworks, frameworkDownloadError(err, rule.Identifier)
}
if err := validateFramework(receivedFramework); err != nil {
return frameworks, err
@@ -171,7 +171,7 @@ func (policyHandler *PolicyHandler) downloadScanPolicies(ctx context.Context, po
logger.L().Debug("Downloading control", helpers.String("control", policy.Identifier))
receivedControl, err = policyHandler.getters.PolicyGetter.GetControl(policy.Identifier)
if err != nil {
return frameworks, policyDownloadError(err)
return frameworks, controlDownloadError(err, policy.Identifier)
}
if receivedControl != nil {
f.Controls = append(f.Controls, *receivedControl)
@@ -17,10 +17,22 @@ func getScanKind(policyIdentifier []cautils.PolicyIdentifier) apisv1.Notificatio
}
return "unknown"
}
func policyDownloadError(err error) error {
func frameworkDownloadError(err error, fwName string) error {
if strings.Contains(err.Error(), "unsupported protocol scheme") {
err = fmt.Errorf("failed to download from GitHub release, try running with `--use-default` flag")
}
if strings.Contains(err.Error(), "not found") {
err = fmt.Errorf("framework '%s' not found, run `kubescape list frameworks` for available frameworks", fwName)
}
return err
}
func controlDownloadError(err error, controls string) error {
if strings.Contains(err.Error(), "unsupported protocol scheme") {
err = fmt.Errorf("failed to download from GitHub release, try running with `--use-default` flag")
}
if strings.Contains(err.Error(), "not found") {
err = fmt.Errorf("control '%s' not found, run `kubescape list controls` for available controls", controls)
}
return err
}
@@ -89,6 +89,8 @@ func TestPolicyDownloadError(t *testing.T) {
tests := []struct {
err error
want error
name string
kind string
}{
{
err: errors.New("Some error"),
@@ -98,11 +100,31 @@ func TestPolicyDownloadError(t *testing.T) {
err: errors.New("unsupported protocol scheme"),
want: fmt.Errorf("failed to download from GitHub release, try running with `--use-default` flag"),
},
{
err: errors.New("framework 'cis' not found"),
want: fmt.Errorf("framework 'cis' not found, run `kubescape list frameworks` for available frameworks"),
name: "cis",
kind: "framework",
},
{
err: errors.New("control 'c-0005' not found"),
want: fmt.Errorf("control 'c-0005' not found, run `kubescape list controls` for available controls"),
name: "c-0005",
kind: "control",
},
}
for _, tt := range tests {
t.Run("", func(t *testing.T) {
assert.Equal(t, tt.want, policyDownloadError(tt.err))
switch tt.kind {
case "framework":
assert.Equal(t, tt.want, frameworkDownloadError(tt.err, tt.name))
case "control":
assert.Equal(t, tt.want, controlDownloadError(tt.err, tt.name))
default:
assert.Equal(t, tt.want, frameworkDownloadError(tt.err, tt.name))
assert.Equal(t, tt.want, controlDownloadError(tt.err, tt.name))
}
})
}
}
+38 -16
View File
@@ -41,22 +41,27 @@ func (fileHandler *FileResourceHandler) GetResources(ctx context.Context, sessio
for path := range scanInfo.InputPatterns {
var workloadIDToSource map[string]reporthandling.Source
var workloads []workloadinterface.IMetadata
var workloadIDToMappingNodes map[string]cautils.MappingNodes
var err error
if scanInfo.ChartPath != "" && scanInfo.FilePath != "" {
workloadIDToSource, workloads, err = getWorkloadFromHelmChart(ctx, scanInfo.ChartPath, scanInfo.FilePath)
workloadIDToSource, workloads, workloadIDToMappingNodes, err = getWorkloadFromHelmChart(ctx, scanInfo.ChartPath, scanInfo.FilePath)
if err != nil {
// We should probably ignore the error so we can continue scanning other charts
}
} else {
workloadIDToSource, workloads, err = getResourcesFromPath(ctx, scanInfo.InputPatterns[path])
workloadIDToSource, workloads, workloadIDToMappingNodes, err = getResourcesFromPath(ctx, scanInfo.InputPatterns[path])
if err != nil {
return nil, allResources, nil, nil, err
}
}
if len(workloads) == 0 {
logger.L().Debug("path ignored because contains only a non-kubernetes file", helpers.String("path", scanInfo.InputPatterns[path]))
continue
}
for k, v := range workloadIDToSource {
sessionObj.ResourceSource[k] = v
sessionObj.TemplateMapping[k] = workloadIDToMappingNodes[k]
}
// map all resources: map["/apiVersion/version/kind"][]<k8s workloads>
@@ -102,10 +107,10 @@ func (fileHandler *FileResourceHandler) GetResources(ctx context.Context, sessio
func (fileHandler *FileResourceHandler) GetCloudProvider() string {
return ""
}
func getWorkloadFromHelmChart(ctx context.Context, helmPath, workloadPath string) (map[string]reporthandling.Source, []workloadinterface.IMetadata, error) {
func getWorkloadFromHelmChart(ctx context.Context, helmPath, workloadPath string) (map[string]reporthandling.Source, []workloadinterface.IMetadata, map[string]cautils.MappingNodes, error) {
clonedRepo, err := cloneGitRepo(&helmPath)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
if clonedRepo != "" {
defer os.RemoveAll(clonedRepo)
@@ -114,7 +119,7 @@ func getWorkloadFromHelmChart(ctx context.Context, helmPath, workloadPath string
// Get repo root
repoRoot, gitRepo := extractGitRepo(helmPath)
helmSourceToWorkloads, helmSourceToChart := cautils.LoadResourcesFromHelmCharts(ctx, helmPath)
helmSourceToWorkloads, helmSourceToChart, helmSourceToNodes := cautils.LoadResourcesFromHelmCharts(ctx, helmPath)
if clonedRepo != "" {
workloadPath = clonedRepo + workloadPath
@@ -122,27 +127,34 @@ func getWorkloadFromHelmChart(ctx context.Context, helmPath, workloadPath string
wlSource, ok := helmSourceToWorkloads[workloadPath]
if !ok {
return nil, nil, fmt.Errorf("workload %s not found in chart %s", workloadPath, helmPath)
return nil, nil, nil, fmt.Errorf("workload %s not found in chart %s", workloadPath, helmPath)
}
if len(wlSource) != 1 {
return nil, nil, fmt.Errorf("workload %s found multiple times in chart %s", workloadPath, helmPath)
return nil, nil, nil, fmt.Errorf("workload %s found multiple times in chart %s", workloadPath, helmPath)
}
helmChart, ok := helmSourceToChart[workloadPath]
if !ok {
return nil, nil, fmt.Errorf("helmChart not found for workload %s", workloadPath)
return nil, nil, nil, fmt.Errorf("helmChart not found for workload %s", workloadPath)
}
templatesNodes, ok := helmSourceToNodes[workloadPath]
if !ok {
return nil, nil, nil, fmt.Errorf("templatesNodes not found for workload %s", workloadPath)
}
workloadSource := getWorkloadSourceHelmChart(repoRoot, helmPath, gitRepo, helmChart)
workloadIDToSource := make(map[string]reporthandling.Source, 1)
workloadIDToNodes := make(map[string]cautils.MappingNodes, 1)
workloadIDToSource[wlSource[0].GetID()] = workloadSource
workloadIDToNodes[wlSource[0].GetID()] = templatesNodes
workloads := []workloadinterface.IMetadata{}
workloads = append(workloads, wlSource...)
return workloadIDToSource, workloads, nil
return workloadIDToSource, workloads, workloadIDToNodes, nil
}
@@ -176,13 +188,14 @@ func getWorkloadSourceHelmChart(repoRoot string, source string, gitRepo *cautils
}
}
func getResourcesFromPath(ctx context.Context, path string) (map[string]reporthandling.Source, []workloadinterface.IMetadata, error) {
func getResourcesFromPath(ctx context.Context, path string) (map[string]reporthandling.Source, []workloadinterface.IMetadata, map[string]cautils.MappingNodes, error) {
workloadIDToSource := make(map[string]reporthandling.Source, 0)
workloadIDToNodes := make(map[string]cautils.MappingNodes)
workloads := []workloadinterface.IMetadata{}
clonedRepo, err := cloneGitRepo(&path)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
if clonedRepo != "" {
defer os.RemoveAll(clonedRepo)
@@ -266,10 +279,11 @@ func getResourcesFromPath(ctx context.Context, path string) (map[string]reportha
}
// load resources from helm charts
helmSourceToWorkloads, helmSourceToChart := cautils.LoadResourcesFromHelmCharts(ctx, path)
helmSourceToWorkloads, helmSourceToChart, helmSourceToNodes := cautils.LoadResourcesFromHelmCharts(ctx, path)
for source, ws := range helmSourceToWorkloads {
workloads = append(workloads, ws...)
helmChart := helmSourceToChart[source]
templatesNodes := helmSourceToNodes[source]
if clonedRepo != "" {
url, err := gitRepo.GetRemoteUrl()
@@ -280,21 +294,29 @@ func getResourcesFromPath(ctx context.Context, path string) (map[string]reportha
helmChart.Path = strings.TrimSuffix(url, ".git")
repoRoot = ""
source = strings.TrimPrefix(source, fmt.Sprintf("%s/", clonedRepo))
templatesNodes.TemplateFileName = source
}
workloadSource := getWorkloadSourceHelmChart(repoRoot, source, gitRepo, helmChart)
for i := range ws {
workloadIDToSource[ws[i].GetID()] = workloadSource
workloadIDToNodes[ws[i].GetID()] = templatesNodes
// workloadIDToNodes[ws[i].GetID()].Nodes = templatesNodes.Nodes
// workloadIDToNodes[ws[i].GetID()].TemplateFileName = templatesNodes.TemplateFileName
// helmSourceToNodes[source]
}
}
if len(helmSourceToWorkloads) > 0 {
if len(helmSourceToWorkloads) > 0 { // && len(helmSourceToNodes) > 0
logger.L().Debug("helm templates found in local storage", helpers.Int("helmTemplates", len(helmSourceToWorkloads)), helpers.Int("workloads", len(workloads)))
} else {
workloadIDToNodes = nil
}
//patch, get value from env
// Load resources from Kustomize directory
kustomizeSourceToWorkloads, kustomizeDirectoryName := cautils.LoadResourcesFromKustomizeDirectory(ctx, path)
kustomizeSourceToWorkloads, kustomizeDirectoryName := cautils.LoadResourcesFromKustomizeDirectory(ctx, path) //?
// update workloads and workloadIDToSource with workloads from Kustomize Directory
for source, ws := range kustomizeSourceToWorkloads {
@@ -331,7 +353,7 @@ func getResourcesFromPath(ctx context.Context, path string) (map[string]reportha
}
}
return workloadIDToSource, workloads, nil
return workloadIDToSource, workloads, workloadIDToNodes, nil
}
func extractGitRepo(path string) (string, *cautils.LocalGitRepository) {
+15 -13
View File
@@ -9,7 +9,6 @@ import (
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/k8s-interface/k8sinterface"
"github.com/kubescape/k8s-interface/workloadinterface"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/opa-utils/objectsenvelopes"
)
@@ -17,20 +16,23 @@ import (
func cloneGitRepo(path *string) (string, error) {
var clonedDir string
// Clone git repository if needed
gitURL, err := giturl.NewGitAPI(*path)
if err == nil {
logger.L().Info("cloning", helpers.String("repository url", gitURL.GetURL().String()))
cautils.StartSpinner()
clonedDir, err = cloneRepo(gitURL)
cautils.StopSpinner()
if err != nil {
return "", fmt.Errorf("failed to clone git repo '%s', %w", gitURL.GetURL().String(), err)
}
*path = filepath.Join(clonedDir, gitURL.GetPath())
if err != nil {
return "", nil
}
// Clone git repository if needed
logger.L().Start("cloning", helpers.String("repository url", gitURL.GetURL().String()))
clonedDir, err = cloneRepo(gitURL)
if err != nil {
logger.L().StopError("failed to clone git repo", helpers.String("url", gitURL.GetURL().String()), helpers.Error(err))
return "", fmt.Errorf("failed to clone git repo '%s', %w", gitURL.GetURL().String(), err)
}
*path = filepath.Join(clonedDir, gitURL.GetPath())
logger.L().StopSuccess("Done accessing local objects")
return clonedDir, nil
}
@@ -36,8 +36,8 @@ func CollectResources(ctx context.Context, rsrcHandler IResourceHandler, policyI
opaSessionObj.ExternalResources = externalResources
opaSessionObj.ExcludedRules = excludedRulesMap
if (opaSessionObj.K8SResources == nil || len(opaSessionObj.K8SResources) == 0) && (opaSessionObj.ExternalResources == nil || len(opaSessionObj.ExternalResources) == 0) {
return fmt.Errorf("empty list of resources")
if (opaSessionObj.K8SResources == nil || len(opaSessionObj.K8SResources) == 0) && (opaSessionObj.ExternalResources == nil || len(opaSessionObj.ExternalResources) == 0) || len(opaSessionObj.AllResources) == 0 {
return fmt.Errorf("no resources found to scan")
}
return nil
+1 -1
View File
@@ -132,7 +132,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 Kubescape operator installed. To install it, go to\n https://kubescape.io/docs/install-operator/.", hostResources, sessionObj.InfoMap)
cautils.SetInfoMapForResources("This control is scanned exclusively by the Kubescape operator, not the Kubescape CLI. Install the Kubescape operator:\n https://kubescape.io/docs/install-operator/.", hostResources, sessionObj.InfoMap)
}
}
@@ -8,12 +8,16 @@ import (
"path/filepath"
"strings"
"github.com/anchore/clio"
"github.com/anchore/grype/grype/presenter"
"github.com/anchore/grype/grype/presenter/models"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer"
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/imageprinter"
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
"k8s.io/utils/strings/slices"
)
const (
@@ -54,11 +58,41 @@ func (jp *JsonPrinter) Score(score float32) {
fmt.Fprintf(os.Stderr, "\nOverall compliance-score (100- Excellent, 0- All failed): %d\n", cautils.Float32ToInt(score))
}
func (jp *JsonPrinter) convertToImageScanSummary(imageScanData []cautils.ImageScanData) (*imageprinter.ImageScanSummary, error) {
imageScanSummary := imageprinter.ImageScanSummary{
CVEs: []imageprinter.CVE{},
PackageScores: map[string]*imageprinter.PackageScore{},
MapsSeverityToSummary: map[string]*imageprinter.SeveritySummary{},
}
for i := range imageScanData {
if !slices.Contains(imageScanSummary.Images, imageScanData[i].Image) {
imageScanSummary.Images = append(imageScanSummary.Images, imageScanData[i].Image)
}
presenterConfig := imageScanData[i].PresenterConfig
doc, err := models.NewDocument(clio.Identification{}, presenterConfig.Packages, presenterConfig.Context, presenterConfig.Matches, presenterConfig.IgnoredMatches, presenterConfig.MetadataProvider, nil, presenterConfig.DBStatus)
if err != nil {
logger.L().Error(fmt.Sprintf("failed to create document for image: %v", imageScanData[i].Image), helpers.Error(err))
continue
}
CVEs := extractCVEs(doc.Matches)
imageScanSummary.CVEs = append(imageScanSummary.CVEs, CVEs...)
setPkgNameToScoreMap(doc.Matches, imageScanSummary.PackageScores)
setSeverityToSummaryMap(CVEs, imageScanSummary.MapsSeverityToSummary)
}
return &imageScanSummary, nil
}
func (jp *JsonPrinter) ActionPrint(ctx context.Context, opaSessionObj *cautils.OPASessionObj, imageScanData []cautils.ImageScanData) {
var err error
if opaSessionObj != nil {
err = printConfigurationsScanning(opaSessionObj, ctx, jp)
err = printConfigurationsScanning(opaSessionObj, ctx, imageScanData, jp)
} else if imageScanData != nil {
err = jp.PrintImageScan(ctx, imageScanData[0].PresenterConfig)
} else {
@@ -73,16 +107,67 @@ func (jp *JsonPrinter) ActionPrint(ctx context.Context, opaSessionObj *cautils.O
printer.LogOutputFile(jp.writer.Name())
}
func printConfigurationsScanning(opaSessionObj *cautils.OPASessionObj, ctx context.Context, jp *JsonPrinter) error {
r, err := json.Marshal(FinalizeResults(opaSessionObj))
if err != nil {
return err
func printConfigurationsScanning(opaSessionObj *cautils.OPASessionObj, ctx context.Context, imageScanData []cautils.ImageScanData, jp *JsonPrinter) error {
if imageScanData != nil {
imageScanSummary, err := jp.convertToImageScanSummary(imageScanData)
if err != nil {
logger.L().Error("failed to convert to image scan summary", helpers.Error(err))
return err
}
opaSessionObj.Report.SummaryDetails.Vulnerabilities.MapsSeverityToSummary = convertToReportSummary(imageScanSummary.MapsSeverityToSummary)
opaSessionObj.Report.SummaryDetails.Vulnerabilities.CVESummary = convertToCVESummary(imageScanSummary.CVEs)
opaSessionObj.Report.SummaryDetails.Vulnerabilities.PackageScores = convertToPackageScores(imageScanSummary.PackageScores)
opaSessionObj.Report.SummaryDetails.Vulnerabilities.Images = imageScanSummary.Images
}
r, err := json.Marshal(FinalizeResults(opaSessionObj))
_, err = jp.writer.Write(r)
return err
}
func convertToPackageScores(packageScores map[string]*imageprinter.PackageScore) map[string]*reportsummary.PackageSummary {
convertedPackageScores := make(map[string]*reportsummary.PackageSummary)
for pkg, score := range packageScores {
convertedPackageScores[pkg] = &reportsummary.PackageSummary{
Name: score.Name,
Version: score.Version,
Score: score.Score,
MapSeverityToCVEsNumber: score.MapSeverityToCVEsNumber,
}
}
return convertedPackageScores
}
func convertToCVESummary(cves []imageprinter.CVE) []reportsummary.CVESummary {
cveSummary := make([]reportsummary.CVESummary, len(cves))
i := 0
for _, cve := range cves {
var a reportsummary.CVESummary
a.Severity = cve.Severity
a.ID = cve.ID
a.Package = cve.Package
a.Version = cve.Version
a.FixVersions = cve.FixVersions
a.FixedState = cve.FixedState
cveSummary[i] = a
i++
}
return cveSummary
}
func convertToReportSummary(input map[string]*imageprinter.SeveritySummary) map[string]*reportsummary.SeveritySummary {
output := make(map[string]*reportsummary.SeveritySummary)
for key, value := range input {
output[key] = &reportsummary.SeveritySummary{
NumberOfCVEs: value.NumberOfCVEs,
NumberOfFixableCVEs: value.NumberOfFixableCVEs,
}
}
return output
}
func (jp *JsonPrinter) PrintImageScan(ctx context.Context, scanResults *models.PresenterConfig) error {
if scanResults == nil {
return fmt.Errorf("no image vulnerability data provided")
@@ -5,6 +5,8 @@ import (
"os"
"testing"
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/imageprinter"
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
"github.com/stretchr/testify/assert"
)
@@ -83,3 +85,110 @@ func TestScore_Json(t *testing.T) {
})
}
}
func TestConvertToCVESummary(t *testing.T) {
cves := []imageprinter.CVE{
{
Severity: "High",
ID: "CVE-2021-1234",
Package: "example-package",
Version: "1.0.0",
FixVersions: []string{"1.0.1", "1.0.2"},
FixedState: "true",
},
{
Severity: "Medium",
ID: "CVE-2021-5678",
Package: "another-package",
Version: "2.0.0",
FixVersions: []string{"2.0.1"},
FixedState: "false",
},
}
want := []reportsummary.CVESummary{
{
Severity: "High",
ID: "CVE-2021-1234",
Package: "example-package",
Version: "1.0.0",
FixVersions: []string{"1.0.1", "1.0.2"},
FixedState: "true",
},
{
Severity: "Medium",
ID: "CVE-2021-5678",
Package: "another-package",
Version: "2.0.0",
FixVersions: []string{"2.0.1"},
FixedState: "false",
},
}
got := convertToCVESummary(cves)
assert.Equal(t, want, got)
}
func TestConvertToPackageScores(t *testing.T) {
packageScores := map[string]*imageprinter.PackageScore{
"example-package": {
Name: "example-package",
Version: "1.0.0",
Score: 80.0,
MapSeverityToCVEsNumber: map[string]int{"High": 2, "Medium": 1},
},
"another-package": {
Name: "another-package",
Version: "2.0.0",
Score: 60.0,
MapSeverityToCVEsNumber: map[string]int{"High": 1, "Medium": 0},
},
}
want := map[string]*reportsummary.PackageSummary{
"example-package": {
Name: "example-package",
Version: "1.0.0",
Score: 80.0,
MapSeverityToCVEsNumber: map[string]int{"High": 2, "Medium": 1},
},
"another-package": {
Name: "another-package",
Version: "2.0.0",
Score: 60.0,
MapSeverityToCVEsNumber: map[string]int{"High": 1, "Medium": 0},
},
}
got := convertToPackageScores(packageScores)
assert.Equal(t, want, got)
}
func TestConvertToReportSummary(t *testing.T) {
input := map[string]*imageprinter.SeveritySummary{
"High": &imageprinter.SeveritySummary{
NumberOfCVEs: 10,
NumberOfFixableCVEs: 5,
},
"Medium": &imageprinter.SeveritySummary{
NumberOfCVEs: 5,
NumberOfFixableCVEs: 2,
},
}
want := map[string]*reportsummary.SeveritySummary{
"High": &reportsummary.SeveritySummary{
NumberOfCVEs: 10,
NumberOfFixableCVEs: 5,
},
"Medium": &reportsummary.SeveritySummary{
NumberOfCVEs: 5,
NumberOfFixableCVEs: 2,
},
}
got := convertToReportSummary(input)
assert.Equal(t, want, got)
}
+2 -1
View File
@@ -14,6 +14,7 @@ import (
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer"
"github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils"
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
"github.com/johnfercher/maroto/pkg/color"
@@ -168,7 +169,7 @@ func (pp *PdfPrinter) printHeader(m pdf.Maroto) {
// printFramework prints the PDF frameworks after the PDF header
func (pp *PdfPrinter) printFramework(m pdf.Maroto, frameworks []reportsummary.IFrameworkSummary) {
m.Row(10, func() {
m.Text(frameworksScoresToString(frameworks), props.Text{
m.Text(utils.FrameworksScoresToString(frameworks), props.Text{
Align: consts.Center,
Size: 8,
Family: consts.Arial,
@@ -7,6 +7,7 @@ import (
"sort"
"strings"
"github.com/anchore/clio"
"github.com/anchore/grype/grype/presenter/models"
"github.com/enescakir/emoji"
"github.com/jwalton/gchalk"
@@ -32,15 +33,15 @@ const (
var _ printer.IPrinter = &PrettyPrinter{}
type PrettyPrinter struct {
mainPrinter prettyprinter.MainPrinter
writer *os.File
formatVersion string
viewType cautils.ViewTypes
scanType cautils.ScanTypes
clusterName string
inputPatterns []string
verboseMode bool
printAttackTree bool
scanType cautils.ScanTypes
inputPatterns []string
mainPrinter prettyprinter.MainPrinter
clusterName string
}
func NewPrettyPrinter(verboseMode bool, formatVersion string, attackTree bool, viewType cautils.ViewTypes, scanType cautils.ScanTypes, inputPatterns []string, clusterName string) *PrettyPrinter {
@@ -90,7 +91,7 @@ func (pp *PrettyPrinter) convertToImageScanSummary(imageScanData []cautils.Image
}
presenterConfig := imageScanData[i].PresenterConfig
doc, err := models.NewDocument(presenterConfig.Packages, presenterConfig.Context, presenterConfig.Matches, presenterConfig.IgnoredMatches, presenterConfig.MetadataProvider, nil, presenterConfig.DBStatus)
doc, err := models.NewDocument(clio.Identification{}, presenterConfig.Packages, presenterConfig.Context, presenterConfig.Matches, presenterConfig.IgnoredMatches, presenterConfig.MetadataProvider, nil, presenterConfig.DBStatus)
if err != nil {
logger.L().Error(fmt.Sprintf("failed to create document for image: %v", imageScanData[i].Image), helpers.Error(err))
continue
@@ -165,9 +166,11 @@ 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, fmt.Sprintf("\nKubescape security posture overview for cluster: %s\n\n", pp.clusterName))
if pp.scanType == cautils.ScanTypeCluster {
cautils.InfoDisplay(pp.writer, fmt.Sprintf("\nSecurity 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.ScanTypeRepo {
cautils.InfoDisplay(pp.writer, fmt.Sprintf("\nSecurity posture overview for repo: '%s'\n\n", strings.Join(pp.inputPatterns, ", ")))
} else if pp.scanType == cautils.ScanTypeWorkload {
cautils.InfoDisplay(pp.writer, "Workload security posture overview for:\n")
ns := opaSessionObj.SingleResourceScan.GetNamespace()
@@ -321,23 +324,6 @@ func generateRelatedObjectsStr(workload WorkloadSummary) string {
return relatedStr
}
func frameworksScoresToString(frameworks []reportsummary.IFrameworkSummary) string {
if len(frameworks) == 1 {
if frameworks[0].GetName() != "" {
return fmt.Sprintf("Framework scanned: %s\n", frameworks[0].GetName())
}
} else if len(frameworks) > 1 {
p := "Frameworks scanned: "
i := 0
for ; i < len(frameworks)-1; i++ {
p += fmt.Sprintf("%s (compliance score: %.2f%%), ", frameworks[i].GetName(), frameworks[i].GetComplianceScore())
}
p += fmt.Sprintf("%s (compliance score: %.2f%%)\n", frameworks[i].GetName(), frameworks[i].GetComplianceScore())
return p
}
return ""
}
func getSeparator(sep string) string {
s := ""
for i := 0; i < 80; i++ {
@@ -32,6 +32,7 @@ func (rp *RepoPrinter) PrintCategoriesTables(writer io.Writer, summaryDetails *r
categoriesToCategoryControls := mapCategoryToSummary(summaryDetails.ListControls(), mapRepoControlsToCategories)
tableRended := false
for _, id := range repoCategoriesDisplayOrder {
categoryControl, ok := categoriesToCategoryControls[id]
if !ok {
@@ -42,12 +43,16 @@ func (rp *RepoPrinter) PrintCategoriesTables(writer io.Writer, summaryDetails *r
continue
}
rp.renderSingleCategoryTable(categoryControl.CategoryName, mapCategoryToType[id], writer, categoryControl.controlSummaries, utils.MapInfoToPrintInfoFromIface(categoryControl.controlSummaries))
tableRended = tableRended || rp.renderSingleCategoryTable(categoryControl.CategoryName, mapCategoryToType[id], writer, categoryControl.controlSummaries, utils.MapInfoToPrintInfoFromIface(categoryControl.controlSummaries))
}
if !tableRended {
fmt.Fprintln(writer, gchalk.WithGreen().Bold("All controls passed. No issues found"))
}
}
func (rp *RepoPrinter) renderSingleCategoryTable(categoryName string, categoryType CategoryType, writer io.Writer, controlSummaries []reportsummary.IControlSummary, infoToPrintInfo []utils.InfoStars) {
func (rp *RepoPrinter) renderSingleCategoryTable(categoryName string, categoryType CategoryType, writer io.Writer, controlSummaries []reportsummary.IControlSummary, infoToPrintInfo []utils.InfoStars) bool {
sortControlSummaries(controlSummaries)
headers, columnAligments := initCategoryTableData(categoryType)
@@ -72,10 +77,11 @@ func (rp *RepoPrinter) renderSingleCategoryTable(categoryName string, categoryTy
}
if len(rows) == 0 {
return
return false
}
renderSingleCategory(writer, categoryName, table, rows, infoToPrintInfo)
return true
}
func (rp *RepoPrinter) generateCountingCategoryRow(controlSummary reportsummary.IControlSummary, inputPatterns []string) []string {
@@ -97,9 +97,9 @@ func FrameworksScoresToString(frameworks []reportsummary.IFrameworkSummary) stri
p := "Frameworks scanned: "
i := 0
for ; i < len(frameworks)-1; i++ {
p += fmt.Sprintf("%s (compliance score: %.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 score: %.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 ""
@@ -160,14 +160,18 @@ func failedPathsToString(control *resourcesresults.ResourceAssociatedControl) []
return paths
}
func fixPathsToString(control *resourcesresults.ResourceAssociatedControl) []string {
func fixPathsToString(control *resourcesresults.ResourceAssociatedControl, onlyPath bool) []string {
var paths []string
for j := range control.ResourceAssociatedRules {
for k := range control.ResourceAssociatedRules[j].Paths {
if p := control.ResourceAssociatedRules[j].Paths[k].FixPath.Path; p != "" {
v := control.ResourceAssociatedRules[j].Paths[k].FixPath.Value
paths = append(paths, fmt.Sprintf("%s=%s", p, v))
if onlyPath {
paths = append(paths, p)
} else {
v := control.ResourceAssociatedRules[j].Paths[k].FixPath.Value
paths = append(paths, fmt.Sprintf("%s=%s", p, v))
}
}
}
}
@@ -201,7 +205,7 @@ func reviewPathsToString(control *resourcesresults.ResourceAssociatedControl) []
}
func AssistedRemediationPathsToString(control *resourcesresults.ResourceAssociatedControl) []string {
paths := append(fixPathsToString(control), append(deletePathsToString(control), reviewPathsToString(control)...)...)
paths := append(fixPathsToString(control, false), append(deletePathsToString(control), reviewPathsToString(control)...)...)
// TODO - deprecate failedPaths once all controls support review/delete paths
paths = appendFailedPathsIfNotInPaths(paths, failedPathsToString(control))
return paths
@@ -254,16 +254,16 @@ func TestFixPathsToString(t *testing.T) {
}
// Test case 1: Empty ResourceAssociatedRules
actualPaths := fixPathsToString(emptyControl)
actualPaths := fixPathsToString(emptyControl, false)
assert.Nil(t, actualPaths)
// Test case 2: Single ResourceAssociatedRule and one ReviewPath
actualPaths = fixPathsToString(singleRuleControl)
actualPaths = fixPathsToString(singleRuleControl, false)
expectedPath := []string{"fix-path1=fix-path-value1"}
assert.Equal(t, expectedPath, actualPaths)
// Test case 3: Multiple ResourceAssociatedRules and multiple ReviewPaths
actualPaths = fixPathsToString(multipleRulesControl)
actualPaths = fixPathsToString(multipleRulesControl, false)
expectedPath = []string{"fix-path2=fix-path-value2", "fix-path3=fix-path-value3"}
assert.Equal(t, expectedPath, actualPaths)
}
@@ -187,8 +187,10 @@ func (sp *SARIFPrinter) printConfigurationScan(ctx context.Context, opaSessionOb
run := sarif.NewRunWithInformationURI(toolName, toolInfoURI)
basePath := getBasePathFromMetadata(*opaSessionObj)
for resourceID, result := range opaSessionObj.ResourcesResult {
for resourceID, result := range opaSessionObj.ResourcesResult { //
if result.GetStatus(nil).IsFailed() {
helmChartFileType := false
var mappingnodes []map[string]cautils.MappingNode
resourceSource := opaSessionObj.ResourceSource[resourceID]
filepath := resourceSource.RelativePath
@@ -197,9 +199,15 @@ func (sp *SARIFPrinter) printConfigurationScan(ctx context.Context, opaSessionOb
continue
}
// If the fileType is helm chart
if templateNodes, ok := opaSessionObj.TemplateMapping[resourceID]; ok {
mappingnodes = templateNodes.Nodes
helmChartFileType = true
}
rsrcAbsPath := path.Join(basePath, filepath)
locationResolver, err := locationresolver.NewFixPathLocationResolver(rsrcAbsPath)
if err != nil {
locationResolver, err := locationresolver.NewFixPathLocationResolver(rsrcAbsPath) //
if err != nil && !helmChartFileType {
logger.L().Debug("failed to create location resolver", helpers.Error(err))
continue
}
@@ -208,12 +216,24 @@ func (sp *SARIFPrinter) printConfigurationScan(ctx context.Context, opaSessionOb
ac := toPin
if ac.GetStatus(nil).IsFailed() {
var location locationresolver.Location
ctl := opaSessionObj.Report.SummaryDetails.Controls.GetControl(reportsummary.EControlCriteriaID, ac.GetID())
location := sp.resolveFixLocation(opaSessionObj, locationResolver, &ac, resourceID)
if helmChartFileType {
for _, subfileNodes := range mappingnodes {
// first get the failed path, then if cannot find it, use the Fix path, cui it to find the closest error.
location, split := resolveFixLocation(subfileNodes, &ac)
sp.addRule(run, ctl)
result := sp.addResult(run, ctl, filepath, location)
collectFixesFromMappingNodes(ctx, result, ac, opaSessionObj, resourceID, filepath, rsrcAbsPath, location, subfileNodes, split)
}
} else {
location = sp.resolveFixLocation(opaSessionObj, locationResolver, &ac, resourceID)
sp.addRule(run, ctl)
result := sp.addResult(run, ctl, filepath, location)
collectFixes(ctx, result, ac, opaSessionObj, resourceID, filepath, rsrcAbsPath)
}
sp.addRule(run, ctl)
result := sp.addResult(run, ctl, filepath, location)
collectFixes(ctx, result, ac, opaSessionObj, resourceID, filepath)
}
}
}
@@ -257,6 +277,56 @@ func (sp *SARIFPrinter) resolveFixLocation(opaSessionObj *cautils.OPASessionObj,
return location
}
func getFixPath(ac *resourcesresults.ResourceAssociatedControl, onlyPath bool) string {
fixPaths := failedPathsToString(ac)
if len(fixPaths) == 0 {
fixPaths = fixPathsToString(ac, onlyPath)
}
var fixPath string
if len(fixPaths) > 0 {
fixPath = fixPaths[0]
}
return fixPath
}
func resolveFixLocation(mappingnodes map[string]cautils.MappingNode, ac *resourcesresults.ResourceAssociatedControl) (locationresolver.Location, int) {
defaultLocation := locationresolver.Location{Line: 1, Column: 1}
fixPath := getFixPath(ac, true)
if fixPath == "" {
return defaultLocation, -1
}
location, split := getLocationFromMappingNodes(mappingnodes, fixPath)
return location, split
}
func getLocationFromNode(node cautils.MappingNode, path string) locationresolver.Location {
line := node.TemplateLineNumber
column := (len(strings.Split(path, "."))-1)*2 + 1 //column begins with 1 instead of 0
return locationresolver.Location{Line: line, Column: column}
}
func getLocationFromMappingNodes(mappingnodes map[string]cautils.MappingNode, fixPath string) (locationresolver.Location, int) {
var location locationresolver.Location
// If cannot match any node, return default location
location = locationresolver.Location{Line: 1, Column: 1}
split := -1
if node, ok := mappingnodes[fixPath]; ok {
location = getLocationFromNode(node, fixPath)
} else {
fields := strings.Split(fixPath, ".")
for i := len(fields) - 1; i >= 0; i-- {
field := fields[:i]
closestPath := strings.Join(field, ".")
if node, ok := mappingnodes[closestPath]; ok {
location = getLocationFromNode(node, closestPath)
split = i
break
}
}
}
return location, split
}
func addFix(result *sarif.Result, filepath string, startLine, startColumn, endLine, endColumn int, text string) {
// Create a new replacement with the specified start and end lines and columns, and the inserted text.
replacement := sarif.NewReplacement(
@@ -337,33 +407,37 @@ func collectDiffs(dmp *diffmatchpatch.DiffMatchPatch, diffs []diffmatchpatch.Dif
}
}
func collectFixes(ctx context.Context, result *sarif.Result, ac resourcesresults.ResourceAssociatedControl, opaSessionObj *cautils.OPASessionObj, resourceID string, filepath string) {
func collectFixes(ctx context.Context, result *sarif.Result, ac resourcesresults.ResourceAssociatedControl, opaSessionObj *cautils.OPASessionObj, resourceID string, filepath string, rsrcAbsPath string) {
for _, rule := range ac.ResourceAssociatedRules {
if !rule.GetStatus(nil).IsFailed() {
continue
}
for _, rulePaths := range rule.Paths {
if rulePaths.FixPath.Path == "" {
continue
}
// if strings.HasPrefix(rulePaths.FixPath.Value, fixhandler.UserValuePrefix) {
// continue
// }
documentIndex, ok := getDocIndex(opaSessionObj, resourceID)
if !ok {
fixPath := rulePaths.FixPath.Path
if fixPath == "" {
continue
}
yamlExpression := fixhandler.FixPathToValidYamlExpression(rulePaths.FixPath.Path, rulePaths.FixPath.Value, documentIndex)
fileAsString, err := fixhandler.GetFileString(filepath)
fileAsString, err := fixhandler.GetFileString(rsrcAbsPath)
if err != nil {
logger.L().Debug("failed to access "+filepath, helpers.Error(err))
continue
}
fixedYamlString, err := fixhandler.ApplyFixToContent(ctx, fileAsString, yamlExpression)
var fixedYamlString string
// if strings.HasPrefix(rulePaths.FixPath.Value, fixhandler.UserValuePrefix) {
// continue
// }
documentIndex, ok := getDocIndex(opaSessionObj, resourceID)
if !ok {
continue
}
yamlExpression := fixhandler.FixPathToValidYamlExpression(fixPath, rulePaths.FixPath.Value, documentIndex)
fixedYamlString, err = fixhandler.ApplyFixToContent(ctx, fileAsString, yamlExpression)
if err != nil {
logger.L().Debug("failed to fix "+filepath+" with "+yamlExpression, helpers.Error(err))
continue
@@ -376,6 +450,98 @@ func collectFixes(ctx context.Context, result *sarif.Result, ac resourcesresults
}
}
func collectFixesFromMappingNodes(ctx context.Context, result *sarif.Result, ac resourcesresults.ResourceAssociatedControl, opaSessionObj *cautils.OPASessionObj, resourceID string, filepath string, rsrcAbsPath string, location locationresolver.Location, subFileNodes map[string]cautils.MappingNode, split int) {
for _, rule := range ac.ResourceAssociatedRules {
if !rule.GetStatus(nil).IsFailed() {
continue
}
for _, rulePaths := range rule.Paths {
fixPath := rulePaths.FixPath.Path
if fixPath == "" {
continue
}
fileAsString, err := fixhandler.GetFileString(rsrcAbsPath)
if err != nil {
logger.L().Debug("failed to access "+filepath, helpers.Error(err))
continue
}
var fixedYamlString string
fixValue := rulePaths.FixPath.Value
if split == -1 { //replaceNode
node := subFileNodes[fixPath]
fixedYamlString = formReplaceFixedYamlString(node, fileAsString, location, fixValue, fixPath)
} else { //insertNode
maxLineNumber := getTheLocationOfAddPart(split, fixPath, subFileNodes)
fixedYamlString = applyFixToContent(split, fixPath, fileAsString, maxLineNumber, fixValue)
}
dmp := diffmatchpatch.New()
diffs := dmp.DiffMain(fileAsString, fixedYamlString, false)
collectDiffs(dmp, diffs, result, filepath, fileAsString)
}
}
}
func applyFixToContent(split int, fixPath string, fileAsString string, addLine int, value string) string {
addLines := make([]string, 0)
fields := strings.Split(fixPath, ".")
for i := split; i < len(fields); i++ {
field := fields[i]
var addedLine string
if i != len(fields)-1 {
addedLine = strings.Repeat(" ", (i*2)) + field + ":"
} else {
addedLine = strings.Repeat(" ", (i*2)) + field + ": " + value
}
addLines = append(addLines, addedLine)
}
fixedYamlString := formAddFixedYamlString(fileAsString, addLine, addLines)
return fixedYamlString
}
func formReplaceFixedYamlString(node cautils.MappingNode, fileAsString string, location locationresolver.Location, fixValue string, fixPath string) string {
replcaedValue := node.Value
yamlLines := strings.Split(fileAsString, "\n")
if replcaedValue == "" {
yamlLines[location.Line] = yamlLines[location.Line] + " # This is the suggested modification, the value for " + fixPath + " is " + fixValue + "\n"
} else {
replacedLine := "# This is the suggested modification\n" + yamlLines[location.Line]
newLine := strings.Replace(replacedLine, replcaedValue, fixValue, -1)
yamlLines[location.Line] = newLine
}
fixedYamlString := strings.Join(yamlLines, "\n")
return fixedYamlString
}
func formAddFixedYamlString(fileAsString string, addLine int, addLines []string) string {
yamlLines := strings.Split(fileAsString, "\n")
newYamlLines := append(yamlLines[:addLine], "# This is the suggested modification")
newYamlLines = append(newYamlLines, addLines...)
yamlLines = strings.Split(fileAsString, "\n")
newYamlLines = append(newYamlLines, yamlLines[addLine:]...)
fixedYamlString := strings.Join(newYamlLines, "\n")
return fixedYamlString
}
func getTheLocationOfAddPart(split int, fixPath string, mappingnodes map[string]cautils.MappingNode) int {
fields := strings.Split(fixPath, ".")
field := fields[:split]
closestPath := strings.Join(field, ".")
maxLineNumber := -1
for k, v := range mappingnodes {
if strings.Index(k, closestPath) == 0 {
if v.TemplateLineNumber > maxLineNumber {
maxLineNumber = v.TemplateLineNumber
}
}
}
return maxLineNumber
}
func getDocIndex(opaSessionObj *cautils.OPASessionObj, resourceID string) (int, bool) {
resource := opaSessionObj.AllResources[resourceID]
localworkload, ok := resource.(*localworkload.LocalWorkload)
+1 -1
View File
@@ -128,7 +128,7 @@ func NewPrinter(ctx context.Context, printFormat string, scanInfo *cautils.ScanI
if printFormat != printer.PrettyFormat {
logger.L().Ctx(ctx).Warning(fmt.Sprintf("Invalid format \"%s\", default format \"pretty-printer\" is applied", printFormat))
}
return printerv2.NewPrettyPrinter(scanInfo.VerboseMode, scanInfo.FormatVersion, scanInfo.PrintAttackTree, cautils.ViewTypes(scanInfo.View), scanInfo.ScanType, nil, clusterName)
return printerv2.NewPrettyPrinter(scanInfo.VerboseMode, scanInfo.FormatVersion, scanInfo.PrintAttackTree, cautils.ViewTypes(scanInfo.View), scanInfo.ScanType, scanInfo.InputPatterns, clusterName)
}
}