From f64586adc562834695367c447811501ed40011dc Mon Sep 17 00:00:00 2001 From: VaibhavMalik4187 Date: Sat, 30 Sep 2023 11:24:31 +0530 Subject: [PATCH] Added support for YAML as an output format This commit adds the support for using yaml as an output format to store the reports. For Example: kubescape scan image nginx -f yaml The report will be stored in the "report.yaml" file. Fixes: https://github.com/kubescape/kubescape/issues/1395 Signed-off-by: VaibhavMalik4187 --- cmd/scan/scan.go | 2 +- .../resultshandling/printer/printresults.go | 1 + .../resultshandling/printer/v2/yamlprinter.go | 128 ++++++++++++++++++ core/pkg/resultshandling/results.go | 4 +- 4 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 core/pkg/resultshandling/printer/v2/yamlprinter.go diff --git a/cmd/scan/scan.go b/cmd/scan/scan.go index 9d13dd1c1c..08dcb7e2a4 100644 --- a/cmd/scan/scan.go +++ b/cmd/scan/scan.go @@ -68,7 +68,7 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command { scanCmd.PersistentFlags().Float32VarP(&scanInfo.ComplianceThreshold, "compliance-threshold", "", 0, "Compliance threshold is the percent below which the command fails and returns exit code 1") scanCmd.PersistentFlags().StringVar(&scanInfo.FailThresholdSeverity, "severity-threshold", "", "Severity threshold is the severity of failed controls at which the command fails and returns exit code 1") - scanCmd.PersistentFlags().StringVarP(&scanInfo.Format, "format", "f", "", `Output file format. Supported formats: "pretty-printer", "json", "junit", "prometheus", "pdf", "html", "sarif"`) + scanCmd.PersistentFlags().StringVarP(&scanInfo.Format, "format", "f", "", `Output file format. Supported formats: "pretty-printer", "json", "junit", "prometheus", "pdf", "html", "sarif", "yaml"`) scanCmd.PersistentFlags().StringVar(&scanInfo.IncludeNamespaces, "include-namespaces", "", "scan specific namespaces. e.g: --include-namespaces ns-a,ns-b") scanCmd.PersistentFlags().BoolVarP(&scanInfo.Local, "keep-local", "", false, "If you do not want your Kubescape results reported to configured backend.") scanCmd.PersistentFlags().StringVarP(&scanInfo.Output, "output", "o", "", "Output file. Print output to file and not stdout") diff --git a/core/pkg/resultshandling/printer/printresults.go b/core/pkg/resultshandling/printer/printresults.go index b7a64e779e..1ce2df841f 100644 --- a/core/pkg/resultshandling/printer/printresults.go +++ b/core/pkg/resultshandling/printer/printresults.go @@ -21,6 +21,7 @@ const ( PdfFormat string = "pdf" HtmlFormat string = "html" SARIFFormat string = "sarif" + YamlFormat string = "yaml" ) type IPrinter interface { diff --git a/core/pkg/resultshandling/printer/v2/yamlprinter.go b/core/pkg/resultshandling/printer/v2/yamlprinter.go new file mode 100644 index 0000000000..e8f1bbf24e --- /dev/null +++ b/core/pkg/resultshandling/printer/v2/yamlprinter.go @@ -0,0 +1,128 @@ +package printer + +import ( + "context" + "encoding/json" + "sigs.k8s.io/yaml" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "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/v2/core/cautils" + "github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer" +) + +const ( + yamlOutputFile = "report" + yamlOutputExt = ".yaml" +) + +var _ printer.IPrinter = &YamlPrinter{} + +type YamlPrinter struct { + writer *os.File +} + +func NewYamlPrinter() *YamlPrinter { + return &YamlPrinter{} +} + +func (yp *YamlPrinter) SetWriter(ctx context.Context, outputFile string) { + if strings.TrimSpace(outputFile) == "" { + outputFile = yamlOutputFile + } + if filepath.Ext(strings.TrimSpace(outputFile)) != yamlOutputExt { + outputFile = outputFile + yamlOutputExt + } + yp.writer = printer.GetWriter(ctx, outputFile) +} + +func (yp *YamlPrinter) Score(score float32) { + fmt.Fprintf(os.Stderr, "\nOverall compliance-score (100- Excellent, 0- All failed): %d\n", cautils.Float32ToInt(score)) + +} + +func jsonToYAML() () { + // Convert the JSON data in the report.yaml file to YAML data. + fileName := yamlOutputFile + yamlOutputExt; + jsonData, err := ioutil.ReadFile(fileName) + if err != nil { + fmt.Printf("Error reading file: %v\n", err) + return + } + + var data interface{} + + if err := yaml.Unmarshal(jsonData, &data); err != nil { + return + } + + yamlData, err := yaml.Marshal(data) + if err != nil { + return + } + + // Overwrite the file with the new YAML content + err = ioutil.WriteFile(fileName, yamlData, os.ModePerm) + if err != nil { + fmt.Printf("Error writing YAML to file: %v\n", err) + return + } +} + +func (yp *YamlPrinter) ActionPrint(ctx context.Context, opaSessionObj *cautils.OPASessionObj, imageScanData []cautils.ImageScanData) { + var err error + if opaSessionObj != nil { + err = printYamlConfigurationsScanning(opaSessionObj, ctx, yp) + } else if imageScanData != nil { + err = yp.PrintImageScan(ctx, imageScanData[0].PresenterConfig) + } else { + err = fmt.Errorf("no data provided") + } + + if err != nil { + logger.L().Ctx(ctx).Error("failed to write results in yaml format", helpers.Error(err)) + return + } + + // Convert JSON to YAML + jsonToYAML() + + printer.LogOutputFile(yp.writer.Name()) +} + +func printYamlConfigurationsScanning(opaSessionObj *cautils.OPASessionObj, ctx context.Context, yp *YamlPrinter) error { + r, err := json.Marshal(FinalizeResults(opaSessionObj)) + if err != nil { + return err + } + + _, err = yp.writer.Write(r) + return err +} + +func (yp *YamlPrinter) PrintImageScan(ctx context.Context, scanResults *models.PresenterConfig) error { + if scanResults == nil { + return fmt.Errorf("no image vulnerability data provided") + } + + // Since grype/presenter doesn't have yaml config, use JSON config. + presenterConfig, err := presenter.ValidatedConfig("json", "", false) + if err != nil { + return err + } + + pres := presenter.GetPresenter(presenterConfig, *scanResults) + + return pres.Present(yp.writer) +} + +func (yp *YamlPrinter) PrintNextSteps() { + +} diff --git a/core/pkg/resultshandling/results.go b/core/pkg/resultshandling/results.go index 18528a419a..52284d0083 100644 --- a/core/pkg/resultshandling/results.go +++ b/core/pkg/resultshandling/results.go @@ -124,6 +124,8 @@ func NewPrinter(ctx context.Context, printFormat, formatVersion string, verboseM return printerv2.NewHtmlPrinter() case printer.SARIFFormat: return printerv2.NewSARIFPrinter() + case printer.YamlFormat: + return printerv2.NewYamlPrinter() default: if printFormat != printer.PrettyFormat { logger.L().Ctx(ctx).Warning(fmt.Sprintf("Invalid format \"%s\", default format \"pretty-printer\" is applied", printFormat)) @@ -136,7 +138,7 @@ func ValidatePrinter(scanType cautils.ScanTypes, scanContext cautils.ScanningCon if scanType == cautils.ScanTypeImage { // supported types for image scanning switch printFormat { - case printer.JsonFormat, printer.PrettyFormat, printer.SARIFFormat: + case printer.JsonFormat, printer.PrettyFormat, printer.SARIFFormat, printer.YamlFormat: return nil default: return fmt.Errorf("format \"%s\"is not supported for image scanning", printFormat)