Compare commits

..

3 Commits

Author SHA1 Message Date
dwertent
ff0264ee15 extend exceptions support 2022-02-28 18:19:17 +02:00
dwertent
544a19906e update junit 2022-02-27 17:29:42 +02:00
dwertent
208bb25118 fixed junit support 2022-02-27 16:33:21 +02:00
21 changed files with 428 additions and 713 deletions

View File

@@ -71,6 +71,7 @@ Want to contribute? Want to discuss something? Have an issue?
* [Scan Kubescape on an air-gapped environment (offline support)](https://youtu.be/IGXL9s37smM)
* [Managing exceptions in the Kubescape SaaS version](https://youtu.be/OzpvxGmCR80)
* [Configure and run customized frameworks](https://youtu.be/12Sanq_rEhs)
* Customize controls configurations. [Kubescape CLI](https://youtu.be/955psg6TVu4), [Kubescape SaaS](https://youtu.be/lIMVSVhH33o)
## Install on Windows
@@ -109,7 +110,7 @@ Set-ExecutionPolicy RemoteSigned -scope CurrentUser
| `--use-artifacts-from` | | Load artifacts (frameworks, control-config, exceptions) from local directory. If not used will download them | |
| `--use-default` | `false` | Load local framework object from default path. If not used will download latest | `true`/`false` |
| `--exceptions` | | Path to an exceptions obj, [examples](https://github.com/armosec/kubescape/tree/master/examples/exceptions/README.md). Default will download exceptions from Kubescape SaaS ||
| `--controls-config` | | Path to a controls-config obj. If not set will download controls-config from ARMO management portal | |
| `--controls-config` | | Path to a controls-config obj. If not set will download controls-config from ARMO management portal. [docs](https://hub.armo.cloud/docs/configuration-parameters) | |
| `--submit` | `false` | If set, Kubescape will send the scan results to Armo management portal where you can see the results in a user-friendly UI, choose your preferred compliance framework, check risk results history and trends, manage exceptions, get remediation recommendations and much more. By default the results are not sent | `true`/`false` |
| `--keep-local` | `false` | Kubescape will not send scan results to Armo management portal. Use this flag if you ran with the `--submit` flag in the past and you do not want to submit your current scan results | `true`/`false` |
| `--account` | | Armo portal account ID. Default will load account ID from configMap or config file | |

View File

@@ -126,6 +126,13 @@ func (armoAPI *ArmoAPI) Post(fullURL string, headers map[string]string, body []b
return HttpPost(armoAPI.httpClient, fullURL, headers, body)
}
func (armoAPI *ArmoAPI) Delete(fullURL string, headers map[string]string) (string, error) {
if headers == nil {
headers = make(map[string]string)
}
armoAPI.appendAuthHeaders(headers)
return HttpDelete(armoAPI.httpClient, fullURL, headers)
}
func (armoAPI *ArmoAPI) Get(fullURL string, headers map[string]string) (string, error) {
if headers == nil {
headers = make(map[string]string)
@@ -293,7 +300,7 @@ func (armoAPI *ArmoAPI) PostExceptions(exceptions []armotypes.PostureExceptionPo
if err != nil {
return err
}
_, err = armoAPI.Post(armoAPI.postExceptionsURL(), map[string]string{"Content-Type": "application/json"}, ex)
_, err = armoAPI.Post(armoAPI.exceptionsURL(""), map[string]string{"Content-Type": "application/json"}, ex)
if err != nil {
return err
}
@@ -301,6 +308,14 @@ func (armoAPI *ArmoAPI) PostExceptions(exceptions []armotypes.PostureExceptionPo
return nil
}
func (armoAPI *ArmoAPI) DeleteException(exceptionName string) error {
_, err := armoAPI.Delete(armoAPI.exceptionsURL(exceptionName), nil)
if err != nil {
return err
}
return nil
}
func (armoAPI *ArmoAPI) Login() error {
if armoAPI.accountID == "" {
return fmt.Errorf("failed to login, missing accountID")

View File

@@ -56,7 +56,7 @@ func (armoAPI *ArmoAPI) getExceptionsURL(clusterName string) string {
return u.String()
}
func (armoAPI *ArmoAPI) postExceptionsURL() string {
func (armoAPI *ArmoAPI) exceptionsURL(exceptionsPolicyName string) string {
u := url.URL{}
u.Scheme = "https"
u.Host = armoAPI.apiURL
@@ -64,6 +64,10 @@ func (armoAPI *ArmoAPI) postExceptionsURL() string {
q := u.Query()
q.Add("customerGUID", armoAPI.getCustomerGUIDFallBack())
if exceptionsPolicyName != "" { // for delete
q.Add("policyName", exceptionsPolicyName)
}
u.RawQuery = q.Encode()
return u.String()

View File

@@ -47,6 +47,24 @@ func JSONDecoder(origin string) *json.Decoder {
return dec
}
func HttpDelete(httpClient *http.Client, fullURL string, headers map[string]string) (string, error) {
req, err := http.NewRequest("DELETE", fullURL, nil)
if err != nil {
return "", err
}
setHeaders(req, headers)
resp, err := httpClient.Do(req)
if err != nil {
return "", err
}
respStr, err := httpRespToString(resp)
if err != nil {
return "", err
}
return respStr, nil
}
func HttpGetter(httpClient *http.Client, fullURL string, headers map[string]string) (string, error) {
req, err := http.NewRequest("GET", fullURL, nil)

View File

@@ -0,0 +1,7 @@
package clihandler
func CliDelete() error {
tenant := getTenantConfig("", "", getKubernetesApi()) // change k8sinterface
return tenant.DeleteCachedConfig()
}

View File

@@ -1,7 +1,35 @@
package clihandler
func CliDelete() error {
import (
"fmt"
tenant := getTenantConfig("", "", getKubernetesApi()) // change k8sinterface
return tenant.DeleteCachedConfig()
"github.com/armosec/kubescape/cautils/getter"
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/cautils/logger/helpers"
)
func DeleteExceptions(accountID string, exceptions []string) error {
// load cached config
getTenantConfig(accountID, "", getKubernetesApi())
// login kubescape SaaS
armoAPI := getter.GetArmoAPIConnector()
if err := armoAPI.Login(); err != nil {
return err
}
for i := range exceptions {
exceptionName := exceptions[i]
if exceptionName == "" {
continue
}
logger.L().Info("Deleting exception", helpers.String("name", exceptionName))
if err := armoAPI.DeleteException(exceptionName); err != nil {
return fmt.Errorf("failed to delete exception '%s', reason: %s", exceptionName, err.Error())
}
logger.L().Success("Exception deleted successfully")
}
return nil
}

View File

@@ -13,6 +13,7 @@ import (
var listFunc = map[string]func(*cliobjects.ListPolicies) ([]string, error){
"controls": listControls,
"frameworks": listFrameworks,
"exceptions": listExceptions,
}
var listFormatFunc = map[string]func(*cliobjects.ListPolicies, []string){
@@ -60,13 +61,25 @@ func listControls(listPolicies *cliobjects.ListPolicies) ([]string, error) {
return g.ListControls(l)
}
func listExceptions(listPolicies *cliobjects.ListPolicies) ([]string, error) {
// load tenant config
getTenantConfig(listPolicies.Account, "", getKubernetesApi())
var exceptionsNames []string
armoAPI := getExceptionsGetter("")
exceptions, err := armoAPI.GetExceptions("")
if err != nil {
return exceptionsNames, err
}
for i := range exceptions {
exceptionsNames = append(exceptionsNames, exceptions[i].Name)
}
return exceptionsNames, nil
}
func prettyPrintListFormat(listPolicies *cliobjects.ListPolicies, policies []string) {
sep := "\n * "
usageCmd := strings.TrimSuffix(listPolicies.Target, "s")
fmt.Printf("Supported %s:%s%s\n", listPolicies.Target, sep, strings.Join(policies, sep))
fmt.Printf("\nUsage:\n")
fmt.Printf("$ kubescape scan %s \"name\"\n", usageCmd)
fmt.Printf("$ kubescape scan %s \"name-0\",\"name-1\"\n\n", usageCmd)
}
func jsonListFormat(listPolicies *cliobjects.ListPolicies, policies []string) {

View File

@@ -3,3 +3,7 @@ package cliobjects
type Submit struct {
Account string
}
type Delete struct {
Account string
}

57
clihandler/cmd/delete.go Normal file
View File

@@ -0,0 +1,57 @@
package cmd
import (
"fmt"
"strings"
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/clihandler"
"github.com/armosec/kubescape/clihandler/cliobjects"
"github.com/spf13/cobra"
)
var deleteInfo cliobjects.Delete
var deleteExceptionsExamples = `
# Delete single exception
kubescape delete exceptions "exception name"
# Delete multiple exceptions
kubescape delete exceptions "first exception;second exception;third exception"
`
var deleteCmd = &cobra.Command{
Use: "delete <command>",
Short: "Delete configurations in Kubescape SaaS version",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
},
}
var deleteExceptionsCmd = &cobra.Command{
Use: "exceptions <exception name>",
Short: "Delete exceptions from Kubescape SaaS version. Run 'kubescape list exceptions' for all exceptions names",
Example: deleteExceptionsExamples,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return fmt.Errorf("missing exceptions names")
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
exceptionsNames := strings.Split(args[0], ";")
if len(exceptionsNames) == 0 {
logger.L().Fatal("missing exceptions names")
}
if err := clihandler.DeleteExceptions(deleteInfo.Account, exceptionsNames); err != nil {
logger.L().Fatal(err.Error())
}
},
}
func init() {
deleteCmd.PersistentFlags().StringVarP(&deleteInfo.Account, "account", "", "", "Armo portal account ID. Default will load account ID from configMap or config file")
rootCmd.AddCommand(deleteCmd)
deleteCmd.AddCommand(deleteExceptionsCmd)
}

View File

@@ -13,6 +13,7 @@ import (
)
var armoBEURLs = ""
var armoBEURLsDep = ""
var rootInfo cautils.RootInfo
const envFlagUsage = "Send report results to specific URL. Format:<ReportReceiver>,<Backend>,<Frontend>.\n\t\tExample:report.armo.cloud,api.armo.cloud,portal.armo.cloud"
@@ -47,8 +48,12 @@ func init() {
cobra.OnInitialize(initLogger, initLoggerLevel, initEnvironment, initCacheDir)
rootCmd.PersistentFlags().StringVar(&armoBEURLs, "environment", "", envFlagUsage)
rootCmd.PersistentFlags().StringVar(&armoBEURLsDep, "environment", "", envFlagUsage)
rootCmd.PersistentFlags().StringVar(&armoBEURLs, "env", "", envFlagUsage)
rootCmd.PersistentFlags().MarkDeprecated("environment", "use 'env' instead")
rootCmd.PersistentFlags().MarkHidden("environment")
rootCmd.PersistentFlags().MarkHidden("env")
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]")
}
@@ -80,6 +85,9 @@ func initCacheDir() {
logger.L().Debug("cache dir updated", helpers.String("path", getter.DefaultLocalStore))
}
func initEnvironment() {
if armoBEURLsDep != "" {
armoBEURLs = armoBEURLsDep
}
urlSlices := strings.Split(armoBEURLs, ",")
if len(urlSlices) != 1 && len(urlSlices) < 3 {
logger.L().Fatal("expected at least 3 URLs (report, api, frontend, auth)")

View File

@@ -24,17 +24,6 @@
"namespace": "kube-node-lease"
}
}
],
"posturePolicies": [
{
"frameworkName": "NSA"
},
{
"frameworkName": "MITRE"
},
{
"frameworkName": "ArmoBest"
}
]
}
]

View File

@@ -1,125 +0,0 @@
package v1
import (
"encoding/xml"
"fmt"
"os"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/resultshandling/printer"
"github.com/armosec/opa-utils/reporthandling"
)
type JunitPrinter struct {
writer *os.File
}
func NewJunitPrinter() *JunitPrinter {
return &JunitPrinter{}
}
func (junitPrinter *JunitPrinter) SetWriter(outputFile string) {
junitPrinter.writer = printer.GetWriter(outputFile)
}
func (junitPrinter *JunitPrinter) Score(score float32) {
fmt.Fprintf(os.Stderr, "\nOverall risk-score (0- Excellent, 100- All failed): %d\n", int(score))
}
func (junitPrinter *JunitPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
cautils.ReportV2ToV1(opaSessionObj)
junitResult, err := convertPostureReportToJunitResult(opaSessionObj.PostureReport)
if err != nil {
logger.L().Fatal("failed to convert posture report object")
}
postureReportStr, err := xml.Marshal(junitResult)
if err != nil {
logger.L().Fatal("failed to convert posture report object")
}
junitPrinter.writer.Write(postureReportStr)
}
type JUnitTestSuites struct {
XMLName xml.Name `xml:"testsuites"`
Suites []JUnitTestSuite `xml:"testsuite"`
}
// JUnitTestSuite is a single JUnit test suite which may contain many
// testcases.
type JUnitTestSuite struct {
XMLName xml.Name `xml:"testsuite"`
Tests int `xml:"tests,attr"`
Time string `xml:"time,attr"`
Name string `xml:"name,attr"`
Resources int `xml:"resources,attr"`
Excluded int `xml:"excluded,attr"`
Failed int `xml:"filed,attr"`
Properties []JUnitProperty `xml:"properties>property,omitempty"`
TestCases []JUnitTestCase `xml:"testcase"`
}
// JUnitTestCase is a single test case with its result.
type JUnitTestCase struct {
XMLName xml.Name `xml:"testcase"`
Classname string `xml:"classname,attr"`
Name string `xml:"name,attr"`
Time string `xml:"time,attr"`
Resources int `xml:"resources,attr"`
Excluded int `xml:"excluded,attr"`
Failed int `xml:"filed,attr"`
SkipMessage *JUnitSkipMessage `xml:"skipped,omitempty"`
Failure *JUnitFailure `xml:"failure,omitempty"`
}
// JUnitSkipMessage contains the reason why a testcase was skipped.
type JUnitSkipMessage struct {
Message string `xml:"message,attr"`
}
// JUnitProperty represents a key/value pair used to define properties.
type JUnitProperty struct {
Name string `xml:"name,attr"`
Value string `xml:"value,attr"`
}
// JUnitFailure contains data related to a failed test.
type JUnitFailure struct {
Message string `xml:"message,attr"`
Type string `xml:"type,attr"`
Contents string `xml:",chardata"`
}
func convertPostureReportToJunitResult(postureResult *reporthandling.PostureReport) (*JUnitTestSuites, error) {
juResult := JUnitTestSuites{XMLName: xml.Name{Local: "Kubescape scan results"}}
for _, framework := range postureResult.FrameworkReports {
suite := JUnitTestSuite{
Name: framework.Name,
Resources: framework.GetNumberOfResources(),
Excluded: framework.GetNumberOfWarningResources(),
Failed: framework.GetNumberOfFailedResources(),
}
for _, controlReports := range framework.ControlReports {
suite.Tests = suite.Tests + 1
testCase := JUnitTestCase{}
testCase.Name = controlReports.Name
testCase.Classname = "Kubescape"
testCase.Time = postureResult.ReportGenerationTime.String()
if 0 < len(controlReports.RuleReports) && 0 < len(controlReports.RuleReports[0].RuleResponses) {
testCase.Resources = controlReports.GetNumberOfResources()
testCase.Excluded = controlReports.GetNumberOfWarningResources()
testCase.Failed = controlReports.GetNumberOfFailedResources()
failure := JUnitFailure{}
failure.Message = fmt.Sprintf("%d resources failed", testCase.Failed)
for _, ruleResponses := range controlReports.RuleReports[0].RuleResponses {
failure.Contents = fmt.Sprintf("%s\n%s", failure.Contents, ruleResponses.AlertMessage)
}
testCase.Failure = &failure
}
suite.TestCases = append(suite.TestCases, testCase)
}
juResult.Suites = append(juResult.Suites, suite)
}
return &juResult, nil
}

View File

@@ -1,271 +0,0 @@
package v1
import (
"fmt"
"os"
"sort"
"strings"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/resultshandling/printer"
"github.com/armosec/opa-utils/objectsenvelopes"
"github.com/armosec/opa-utils/reporthandling"
"github.com/enescakir/emoji"
"github.com/olekukonko/tablewriter"
)
type PrettyPrinter struct {
writer *os.File
summary Summary
verboseMode bool
sortedControlNames []string
frameworkSummary ResultSummary
}
func NewPrettyPrinter(verboseMode bool) *PrettyPrinter {
return &PrettyPrinter{
verboseMode: verboseMode,
summary: NewSummary(),
}
}
// !!!! DEPRECATED !!!! use v2 package
func (prettyPrinter *PrettyPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
overallRiskScore := opaSessionObj.Report.SummaryDetails.Score
cautils.ReportV2ToV1(opaSessionObj)
// score := calculatePostureScore(opaSessionObj.PostureReport)
failedResources := []string{}
warningResources := []string{}
allResources := []string{}
frameworkNames := []string{}
frameworkScores := []float32{}
for _, frameworkReport := range opaSessionObj.PostureReport.FrameworkReports {
frameworkNames = append(frameworkNames, frameworkReport.Name)
frameworkScores = append(frameworkScores, frameworkReport.Score)
failedResources = reporthandling.GetUniqueResourcesIDs(append(failedResources, frameworkReport.ListResourcesIDs().GetFailedResources()...))
warningResources = reporthandling.GetUniqueResourcesIDs(append(warningResources, frameworkReport.ListResourcesIDs().GetWarningResources()...))
allResources = reporthandling.GetUniqueResourcesIDs(append(allResources, frameworkReport.ListResourcesIDs().GetAllResources()...))
prettyPrinter.summarySetup(frameworkReport, opaSessionObj.AllResources)
}
prettyPrinter.frameworkSummary = ResultSummary{
RiskScore: overallRiskScore,
TotalResources: len(allResources),
TotalFailed: len(failedResources),
TotalWarning: len(warningResources),
}
prettyPrinter.printResults()
prettyPrinter.printSummaryTable(frameworkNames, frameworkScores)
}
func (prettyPrinter *PrettyPrinter) SetWriter(outputFile string) {
prettyPrinter.writer = printer.GetWriter(outputFile)
}
func (prettyPrinter *PrettyPrinter) Score(score float32) {
}
func (prettyPrinter *PrettyPrinter) summarySetup(fr reporthandling.FrameworkReport, allResources map[string]workloadinterface.IMetadata) {
for _, cr := range fr.ControlReports {
// if len(cr.RuleReports) == 0 {
// continue
// }
workloadsSummary := listResultSummary(cr.RuleReports, allResources)
var passedWorkloads map[string][]WorkloadSummary
if prettyPrinter.verboseMode {
passedWorkloads = groupByNamespaceOrKind(workloadsSummary, workloadSummaryPassed)
}
//controlSummary
prettyPrinter.summary[cr.Name] = ResultSummary{
ID: cr.ControlID,
RiskScore: cr.Score,
TotalResources: cr.GetNumberOfResources(),
TotalFailed: cr.GetNumberOfFailedResources(),
TotalWarning: cr.GetNumberOfWarningResources(),
FailedWorkloads: groupByNamespaceOrKind(workloadsSummary, workloadSummaryFailed),
ExcludedWorkloads: groupByNamespaceOrKind(workloadsSummary, workloadSummaryExclude),
PassedWorkloads: passedWorkloads,
Description: cr.Description,
Remediation: cr.Remediation,
ListInputKinds: cr.ListControlsInputKinds(),
}
}
prettyPrinter.sortedControlNames = prettyPrinter.getSortedControlsNames()
}
func (prettyPrinter *PrettyPrinter) printResults() {
for i := 0; i < len(prettyPrinter.sortedControlNames); i++ {
controlSummary := prettyPrinter.summary[prettyPrinter.sortedControlNames[i]]
prettyPrinter.printTitle(prettyPrinter.sortedControlNames[i], &controlSummary)
prettyPrinter.printResources(&controlSummary)
if prettyPrinter.summary[prettyPrinter.sortedControlNames[i]].TotalResources > 0 {
prettyPrinter.printSummary(prettyPrinter.sortedControlNames[i], &controlSummary)
}
}
}
func (prettyPrinter *PrettyPrinter) printSummary(controlName string, controlSummary *ResultSummary) {
cautils.SimpleDisplay(prettyPrinter.writer, "Summary - ")
cautils.SuccessDisplay(prettyPrinter.writer, "Passed:%v ", controlSummary.TotalResources-controlSummary.TotalFailed-controlSummary.TotalWarning)
cautils.WarningDisplay(prettyPrinter.writer, "Excluded:%v ", controlSummary.TotalWarning)
cautils.FailureDisplay(prettyPrinter.writer, "Failed:%v ", controlSummary.TotalFailed)
cautils.InfoDisplay(prettyPrinter.writer, "Total:%v\n", controlSummary.TotalResources)
if controlSummary.TotalFailed > 0 {
cautils.DescriptionDisplay(prettyPrinter.writer, "Remediation: %v\n", controlSummary.Remediation)
}
cautils.DescriptionDisplay(prettyPrinter.writer, "\n")
}
func (prettyPrinter *PrettyPrinter) printTitle(controlName string, controlSummary *ResultSummary) {
cautils.InfoDisplay(prettyPrinter.writer, "[control: %s - %s] ", controlName, getControlURL(controlSummary.ID))
if controlSummary.TotalResources == 0 {
cautils.InfoDisplay(prettyPrinter.writer, "skipped %v\n", emoji.ConfusedFace)
} else if controlSummary.TotalFailed != 0 {
cautils.FailureDisplay(prettyPrinter.writer, "failed %v\n", emoji.SadButRelievedFace)
} else if controlSummary.TotalWarning != 0 {
cautils.WarningDisplay(prettyPrinter.writer, "excluded %v\n", emoji.NeutralFace)
} else {
cautils.SuccessDisplay(prettyPrinter.writer, "passed %v\n", emoji.ThumbsUp)
}
cautils.DescriptionDisplay(prettyPrinter.writer, "Description: %s\n", controlSummary.Description)
}
func (prettyPrinter *PrettyPrinter) printResources(controlSummary *ResultSummary) {
if len(controlSummary.FailedWorkloads) > 0 {
cautils.FailureDisplay(prettyPrinter.writer, "Failed:\n")
prettyPrinter.printGroupedResources(controlSummary.FailedWorkloads)
}
if len(controlSummary.ExcludedWorkloads) > 0 {
cautils.WarningDisplay(prettyPrinter.writer, "Excluded:\n")
prettyPrinter.printGroupedResources(controlSummary.ExcludedWorkloads)
}
if len(controlSummary.PassedWorkloads) > 0 {
cautils.SuccessDisplay(prettyPrinter.writer, "Passed:\n")
prettyPrinter.printGroupedResources(controlSummary.PassedWorkloads)
}
}
func (prettyPrinter *PrettyPrinter) printGroupedResources(workloads map[string][]WorkloadSummary) {
indent := INDENT
for title, rsc := range workloads {
prettyPrinter.printGroupedResource(indent, title, rsc)
}
}
func (prettyPrinter *PrettyPrinter) printGroupedResource(indent string, title string, rsc []WorkloadSummary) {
preIndent := indent
if title != "" {
cautils.SimpleDisplay(prettyPrinter.writer, "%s%s\n", indent, title)
indent += indent
}
for r := range rsc {
relatedObjectsStr := generateRelatedObjectsStr(rsc[r])
cautils.SimpleDisplay(prettyPrinter.writer, fmt.Sprintf("%s%s - %s %s\n", indent, rsc[r].resource.GetKind(), rsc[r].resource.GetName(), relatedObjectsStr))
}
indent = preIndent
}
func generateRelatedObjectsStr(workload WorkloadSummary) string {
relatedStr := ""
if workload.resource.GetObjectType() == workloadinterface.TypeWorkloadObject {
relatedObjects := objectsenvelopes.NewRegoResponseVectorObject(workload.resource.GetObject()).GetRelatedObjects()
for i, related := range relatedObjects {
if ns := related.GetNamespace(); i == 0 && ns != "" {
relatedStr += fmt.Sprintf("Namespace - %s, ", ns)
}
relatedStr += fmt.Sprintf("%s - %s, ", related.GetKind(), related.GetName())
}
}
if relatedStr != "" {
relatedStr = fmt.Sprintf(" [%s]", relatedStr[:len(relatedStr)-2])
}
return relatedStr
}
func generateRow(control string, cs ResultSummary) []string {
row := []string{control}
row = append(row, cs.ToSlice()...)
if cs.TotalResources != 0 {
row = append(row, fmt.Sprintf("%d", int(cs.RiskScore))+"%")
} else {
row = append(row, "skipped")
}
return row
}
func generateHeader() []string {
return []string{"Control Name", "Failed Resources", "Excluded Resources", "All Resources", "% risk-score"}
}
func generateFooter(prettyPrinter *PrettyPrinter) []string {
// Control name | # failed resources | all resources | % success
row := []string{}
row = append(row, "Resource Summary") //fmt.Sprintf(""%d", numControlers"))
row = append(row, fmt.Sprintf("%d", prettyPrinter.frameworkSummary.TotalFailed))
row = append(row, fmt.Sprintf("%d", prettyPrinter.frameworkSummary.TotalWarning))
row = append(row, fmt.Sprintf("%d", prettyPrinter.frameworkSummary.TotalResources))
row = append(row, fmt.Sprintf("%.2f%s", prettyPrinter.frameworkSummary.RiskScore, "%"))
return row
}
func (prettyPrinter *PrettyPrinter) printSummaryTable(frameworksNames []string, frameworkScores []float32) {
// For control scan framework will be nil
prettyPrinter.printFramework(frameworksNames, frameworkScores)
summaryTable := tablewriter.NewWriter(prettyPrinter.writer)
summaryTable.SetAutoWrapText(false)
summaryTable.SetHeader(generateHeader())
summaryTable.SetHeaderLine(true)
alignments := []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER}
summaryTable.SetColumnAlignment(alignments)
for i := 0; i < len(prettyPrinter.sortedControlNames); i++ {
controlSummary := prettyPrinter.summary[prettyPrinter.sortedControlNames[i]]
summaryTable.Append(generateRow(prettyPrinter.sortedControlNames[i], controlSummary))
}
summaryTable.SetFooter(generateFooter(prettyPrinter))
// summaryTable.SetFooter(generateFooter())
summaryTable.Render()
}
func (prettyPrinter *PrettyPrinter) printFramework(frameworksNames []string, frameworkScores []float32) {
if len(frameworksNames) == 1 {
if frameworksNames[0] != "" {
cautils.InfoTextDisplay(prettyPrinter.writer, fmt.Sprintf("FRAMEWORK %s\n", frameworksNames[0]))
}
} else if len(frameworksNames) > 1 {
p := "FRAMEWORKS: "
for i := 0; i < len(frameworksNames)-1; i++ {
p += fmt.Sprintf("%s (risk: %.2f), ", frameworksNames[i], frameworkScores[i])
}
p += fmt.Sprintf("%s (risk: %.2f)\n", frameworksNames[len(frameworksNames)-1], frameworkScores[len(frameworkScores)-1])
cautils.InfoTextDisplay(prettyPrinter.writer, p)
}
}
func (prettyPrinter *PrettyPrinter) getSortedControlsNames() []string {
controlNames := make([]string, 0, len(prettyPrinter.summary))
for k := range prettyPrinter.summary {
controlNames = append(controlNames, k)
}
sort.Strings(controlNames)
return controlNames
}
func getControlURL(controlID string) string {
return fmt.Sprintf("https://hub.armo.cloud/docs/%s", strings.ToLower(controlID))
}

View File

@@ -1,3 +0,0 @@
package v1
var INDENT = " "

View File

@@ -1,11 +0,0 @@
package v1
import (
"github.com/armosec/kubescape/cautils"
)
type SilentPrinter struct {
}
func (silentPrinter *SilentPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
}

View File

@@ -1,54 +0,0 @@
package v1
import (
"fmt"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/opa-utils/reporthandling"
)
type Summary map[string]ResultSummary
func NewSummary() Summary {
return make(map[string]ResultSummary)
}
type ResultSummary struct {
ID string
RiskScore float32
TotalResources int
TotalFailed int
TotalWarning int
Description string
Remediation string
Framework []string
ListInputKinds []string
FailedWorkloads map[string][]WorkloadSummary // <namespace>:[<WorkloadSummary>]
ExcludedWorkloads map[string][]WorkloadSummary // <namespace>:[<WorkloadSummary>]
PassedWorkloads map[string][]WorkloadSummary // <namespace>:[<WorkloadSummary>]
}
type WorkloadSummary struct {
resource workloadinterface.IMetadata
status string
}
func (controlSummary *ResultSummary) ToSlice() []string {
s := []string{}
s = append(s, fmt.Sprintf("%d", controlSummary.TotalFailed))
s = append(s, fmt.Sprintf("%d", controlSummary.TotalWarning))
s = append(s, fmt.Sprintf("%d", controlSummary.TotalResources))
return s
}
func workloadSummaryFailed(workloadSummary *WorkloadSummary) bool {
return workloadSummary.status == reporthandling.StatusFailed
}
func workloadSummaryExclude(workloadSummary *WorkloadSummary) bool {
return workloadSummary.status == reporthandling.StatusWarning
}
func workloadSummaryPassed(workloadSummary *WorkloadSummary) bool {
return workloadSummary.status == reporthandling.StatusPassed
}

View File

@@ -1,85 +0,0 @@
package v1
import (
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/opa-utils/objectsenvelopes"
"github.com/armosec/opa-utils/reporthandling"
)
// Group workloads by namespace - return {"namespace": <[]WorkloadSummary>}
func groupByNamespaceOrKind(resources []WorkloadSummary, status func(workloadSummary *WorkloadSummary) bool) map[string][]WorkloadSummary {
mapResources := make(map[string][]WorkloadSummary)
for i := range resources {
if !status(&resources[i]) {
continue
}
t := resources[i].resource.GetObjectType()
if t == objectsenvelopes.TypeRegoResponseVectorObject && !isKindToBeGrouped(resources[i].resource.GetKind()) {
t = workloadinterface.TypeWorkloadObject
}
switch t { // TODO - find a better way to defind the groups
case workloadinterface.TypeWorkloadObject:
ns := ""
if resources[i].resource.GetNamespace() != "" {
ns = "Namespace " + resources[i].resource.GetNamespace()
}
if r, ok := mapResources[ns]; ok {
r = append(r, resources[i])
mapResources[ns] = r
} else {
mapResources[ns] = []WorkloadSummary{resources[i]}
}
case objectsenvelopes.TypeRegoResponseVectorObject:
group := resources[i].resource.GetKind() + "s"
if r, ok := mapResources[group]; ok {
r = append(r, resources[i])
mapResources[group] = r
} else {
mapResources[group] = []WorkloadSummary{resources[i]}
}
default:
group, _ := k8sinterface.SplitApiVersion(resources[i].resource.GetApiVersion())
if r, ok := mapResources[group]; ok {
r = append(r, resources[i])
mapResources[group] = r
} else {
mapResources[group] = []WorkloadSummary{resources[i]}
}
}
}
return mapResources
}
func isKindToBeGrouped(kind string) bool {
if kind == "Group" || kind == "User" {
return true
}
return false
}
func listResultSummary(ruleReports []reporthandling.RuleReport, allResources map[string]workloadinterface.IMetadata) []WorkloadSummary {
workloadsSummary := []WorkloadSummary{}
for c := range ruleReports {
resourcesIDs := ruleReports[c].ListResourcesIDs()
workloadsSummary = append(workloadsSummary, newListWorkloadsSummary(allResources, resourcesIDs.GetFailedResources(), reporthandling.StatusFailed)...)
workloadsSummary = append(workloadsSummary, newListWorkloadsSummary(allResources, resourcesIDs.GetWarningResources(), reporthandling.StatusWarning)...)
workloadsSummary = append(workloadsSummary, newListWorkloadsSummary(allResources, resourcesIDs.GetPassedResources(), reporthandling.StatusPassed)...)
}
return workloadsSummary
}
func newListWorkloadsSummary(allResources map[string]workloadinterface.IMetadata, resourcesIDs []string, status string) []WorkloadSummary {
workloadsSummary := []WorkloadSummary{}
for _, i := range resourcesIDs {
if r, ok := allResources[i]; ok {
workloadsSummary = append(workloadsSummary, WorkloadSummary{
resource: r,
status: status,
})
}
}
return workloadsSummary
}

View File

@@ -4,73 +4,65 @@ import (
"encoding/xml"
"fmt"
"os"
"strings"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/cautils/logger/helpers"
"github.com/armosec/kubescape/resultshandling/printer"
reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2"
)
/*
riskScore
status
*/
type JunitPrinter struct {
writer *os.File
writer *os.File
verbose bool
}
func NewJunitPrinter() *JunitPrinter {
return &JunitPrinter{}
}
func (junitPrinter *JunitPrinter) SetWriter(outputFile string) {
junitPrinter.writer = printer.GetWriter(outputFile)
}
func (junitPrinter *JunitPrinter) Score(score float32) {
fmt.Fprintf(os.Stderr, "\nOverall risk-score (0- Excellent, 100- All failed): %d\n", int(score))
}
func (junitPrinter *JunitPrinter) FinalizeData(opaSessionObj *cautils.OPASessionObj) {
finalizeReport(opaSessionObj)
}
func (junitPrinter *JunitPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
junitResult, err := convertPostureReportToJunitResult(opaSessionObj.Report)
if err != nil {
logger.L().Fatal("failed to convert posture report object")
}
postureReportStr, err := xml.Marshal(junitResult)
if err != nil {
logger.L().Fatal("failed to convert posture report object")
}
junitPrinter.writer.Write(postureReportStr)
// https://llg.cubic.org/docs/junit/
type JUnitXML struct {
TestSuites JUnitTestSuites `xml:"testsuites"`
}
// JUnitTestSuites represents the test summary
type JUnitTestSuites struct {
XMLName xml.Name `xml:"testsuites"`
Suites []JUnitTestSuite `xml:"testsuite"`
XMLName xml.Name `xml:"testsuites"`
Suites []JUnitTestSuite `xml:"testsuite"` // list of controls
Errors int `xml:"errors,attr"` // total number of tests with error result from all testsuites
Disabled int `xml:"disabled,attr"` // total number of disabled tests from all testsuites
Failures int `xml:"failures,attr"` // total number of failed tests from all testsuites
Tests int `xml:"tests,attr"` // total number of tests from all testsuites. Some software may expect to only see the number of successful tests from all testsuites though
Time string `xml:"time,attr"` // time in seconds to execute all test suites
Name string `xml:"name,attr"` // ? Add framework names ?
}
// JUnitTestSuite is a single JUnit test suite which may contain many
// testcases.
// JUnitTestSuite represents a single control
type JUnitTestSuite struct {
XMLName xml.Name `xml:"testsuite"`
Tests int `xml:"tests,attr"`
Time string `xml:"time,attr"`
Name string `xml:"name,attr"`
Resources int `xml:"resources,attr"`
Excluded int `xml:"excluded,attr"`
Failed int `xml:"filed,attr"`
Name string `xml:"name,attr"` // Full (class) name of the test for non-aggregated testsuite documents. Class name without the package for aggregated testsuites documents. Required
Disabled int `xml:"disabled,attr"` // The total number of disabled tests in the suite. optional. not supported by maven surefire.
Errors int `xml:"errors,attr"` // The total number of tests in the suite that errored
Failures int `xml:"failures,attr"` // The total number of tests in the suite that failed
Hostname string `xml:"hostname,attr"` // Host on which the tests were executed ? cluster name ?
ID int `xml:"id,attr"` // Starts at 0 for the first testsuite and is incremented by 1 for each following testsuite
Skipped string `xml:"skipped,attr"` // The total number of skipped tests
Time string `xml:"time,attr"` // Time taken (in seconds) to execute the tests in the suite
Timestamp string `xml:"timestamp,attr"` // when the test was executed in ISO 8601 format (2014-01-21T16:17:18)
Properties []JUnitProperty `xml:"properties>property,omitempty"`
TestCases []JUnitTestCase `xml:"testcase"`
}
// JUnitTestCase is a single test case with its result.
// JUnitTestCase represents a single resource
type JUnitTestCase struct {
XMLName xml.Name `xml:"testcase"`
Classname string `xml:"classname,attr"`
Name string `xml:"name,attr"`
Time string `xml:"time,attr"`
Resources int `xml:"resources,attr"`
Excluded int `xml:"excluded,attr"`
Failed int `xml:"filed,attr"`
Classname string `xml:"classname,attr"` // Full class name for the class the test method is in. required
Status string `xml:"status,attr"` // Status
Name string `xml:"name,attr"` // Name of the test method, required
Time string `xml:"time,attr"` // Time taken (in seconds) to execute the test. optional
SkipMessage *JUnitSkipMessage `xml:"skipped,omitempty"`
Failure *JUnitFailure `xml:"failure,omitempty"`
}
@@ -93,36 +85,197 @@ type JUnitFailure struct {
Contents string `xml:",chardata"`
}
func convertPostureReportToJunitResult(postureResult *reporthandlingv2.PostureReport) (*JUnitTestSuites, error) {
juResult := JUnitTestSuites{XMLName: xml.Name{Local: "Kubescape scan results"}}
for _, framework := range postureResult.ListFrameworks().All() {
suite := JUnitTestSuite{
Name: framework.GetName(),
Resources: framework.NumberOfResources().All(),
Excluded: framework.NumberOfResources().Excluded(),
Failed: framework.NumberOfResources().Failed(),
}
for _, controlReports := range postureResult.ListControls().All() {
suite.Tests = suite.Tests + 1
testCase := JUnitTestCase{}
testCase.Name = controlReports.GetName()
testCase.Classname = "Kubescape"
testCase.Time = postureResult.ReportGenerationTime.String()
// if 0 < len(controlReports.RuleReports[0].RuleResponses) {
// testCase.Resources = controlReports.NumberOfResources().All()
// testCase.Excluded = controlReports.NumberOfResources().Excluded()
// testCase.Failed = controlReports.NumberOfResources().Failed()
// failure := JUnitFailure{}
// failure.Message = fmt.Sprintf("%d resources failed", testCase.Failed)
// for _, ruleResponses := range controlReports.RuleReports[0].RuleResponses {
// failure.Contents = fmt.Sprintf("%s\n%s", failure.Contents, ruleResponses.AlertMessage)
// }
// testCase.Failure = &failure
// }
suite.TestCases = append(suite.TestCases, testCase)
}
juResult.Suites = append(juResult.Suites, suite)
func NewJunitPrinter(verbose bool) *JunitPrinter {
return &JunitPrinter{
verbose: verbose,
}
return &juResult, nil
}
func (junitPrinter *JunitPrinter) SetWriter(outputFile string) {
junitPrinter.writer = printer.GetWriter(outputFile)
}
func (junitPrinter *JunitPrinter) Score(score float32) {
fmt.Fprintf(os.Stderr, "\nOverall risk-score (0- Excellent, 100- All failed): %d\n", int(score))
}
func (junitPrinter *JunitPrinter) FinalizeData(opaSessionObj *cautils.OPASessionObj) {
finalizeReport(opaSessionObj)
}
func (junitPrinter *JunitPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
junitResult, err := junitPrinter.convertPostureReportToJunitResult(opaSessionObj)
if err != nil {
logger.L().Fatal("failed to build xml result object", helpers.Error(err))
}
postureReportStr, err := xml.Marshal(junitResult)
if err != nil {
logger.L().Fatal("failed to Marshal xml result object", helpers.Error(err))
}
junitPrinter.writer.Write(postureReportStr)
}
func (junitPrinter *JunitPrinter) convertPostureReportToJunitResult(results *cautils.OPASessionObj) (*JUnitTestSuites, error) {
// // Frameworks
// for _, frameworksReports := range results.Report.ListFrameworks().All() {
// fw := JUnitFrameworks{}
// fw.Name = frameworksReports.GetName()
// fw.RiskScore = frameworksReports.GetScore()
// fw.Status = string(frameworksReports.GetStatus().Status())
// juResult.Frameworks = append(juResult.Frameworks, fw)
// }
testSuites := JUnitTestSuites{
XMLName: xml.Name{
Local: "Kubescape scan results",
},
}
testSuites.Failures = results.Report.SummaryDetails.NumberOfResources().Failed()
testSuites.Tests = results.Report.SummaryDetails.NumberOfResources().All()
testSuites.Disabled = results.Report.SummaryDetails.NumberOfResources().Skipped()
// summary.errors =
// summary.Name = "?"
// resources
counter := 0
for resourceID, resourceResult := range results.ResourcesResult {
counter++
// resource data
testSuite := JUnitTestSuite{
XMLName: xml.Name{
Local: resourceID,
},
}
testSuite.Name = resourceID
testSuite.Disabled = 0
testSuite.Errors = 0
testSuite.Failures = len(resourceResult.ListControlsIDs(nil).Failed())
testSuite.Hostname = ""
testSuite.ID = counter
// testSuite.Skipped = ""
testSuite.Time = ""
testSuite.Timestamp = results.PostureReport.ReportGenerationTime.String()
testSuite.Properties = []JUnitProperty{
{
Name: "ID",
Value: resourceID,
},
}
// controls
for _, control := range resourceResult.ListControls() {
testCase := JUnitTestCase{
XMLName: xml.Name{
Local: control.GetName(),
Space: getControlURL(control.GetID()),
},
}
testCase.Name = control.GetName()
testCase.Classname = control.GetID()
testCase.Status = string(control.GetStatus(nil).Status())
if control.GetStatus(nil).IsFailed() {
paths := failedPathsToString(&control)
testCaseFailure := JUnitFailure{}
testCaseFailure.Contents = fmt.Sprintf("More deatiles: %s", getControlURL(control.GetID()))
testCaseFailure.Message = strings.Join(paths, ";")
testCaseFailure.Type = "" // TODO - suppot add/modify
testCase.Failure = &testCaseFailure
}
testSuite.TestCases = append(testSuite.TestCases, testCase)
}
testSuites.Suites = append(testSuites.Suites, testSuite)
}
return &testSuites, nil
}
// func (junitPrinter *JunitPrinter) convertPostureReportToJunitResult(results *cautils.OPASessionObj) (*JUnitTestSuites, error) {
// // // Frameworks
// // for _, frameworksReports := range results.Report.ListFrameworks().All() {
// // fw := JUnitFrameworks{}
// // fw.Name = frameworksReports.GetName()
// // fw.RiskScore = frameworksReports.GetScore()
// // fw.Status = string(frameworksReports.GetStatus().Status())
// // juResult.Frameworks = append(juResult.Frameworks, fw)
// // }
// testSuites := JUnitTestSuites{
// XMLName: xml.Name{
// Local: "Kubescape scan results",
// },
// }
// testSuites.Failures = results.Report.SummaryDetails.NumberOfControls().Failed()
// testSuites.Tests = results.Report.SummaryDetails.NumberOfControls().All()
// testSuites.Disabled = results.Report.SummaryDetails.NumberOfControls().Skipped()
// // summary.errors =
// // summary.Name = "?"
// // controls
// for _, controlIDs := range results.Report.ListControlsIDs().All() {
// controlReport := results.Report.SummaryDetails.Controls.GetControl(reportsummary.EControlCriteriaID, controlIDs)
// // control data
// testSuite := JUnitTestSuite{
// XMLName: xml.Name{
// Local: controlReport.GetName(),
// Space: getControlURL(controlReport.GetID()),
// },
// }
// testSuite.Name = controlReport.GetName()
// testSuite.Disabled = 0
// testSuite.Errors = 0
// testSuite.Failures = controlReport.NumberOfResources().Failed()
// testSuite.Hostname = ""
// testSuite.ID = 0
// // testSuite.Skipped = ""
// testSuite.Time = ""
// testSuite.Timestamp = ""
// testCase.Classname = controlReport.GetID()
// testCase.Url = getControlURL(controlReport.GetID())
// testCase.Name = controlReport.GetName()
// testCase.Status = string(controlReport.GetStatus().Status())
// // resources counters
// testCase.AllResources = controlReport.NumberOfResources().All()
// testCase.Excluded = controlReport.NumberOfResources().Excluded()
// testCase.Failed = controlReport.NumberOfResources().Failed()
// // resources
// var jUnitResources []JUnitResource
// for _, resourceID := range controlReport.ListResourcesIDs().All() {
// result, ok := results.ResourcesResult[resourceID]
// if !ok {
// continue
// }
// if result.GetStatus(nil).IsPassed() && !junitPrinter.verbose { // add passed resources only in verbose mode
// continue
// }
// jUnitResource := JUnitResource{}
// rules := result.ListRulesOfControl(controlReport.GetID(), "")
// for _, rule := range rules {
// jUnitResource.FailedPaths = append(jUnitResource.FailedPaths, rule.Paths...)
// }
// if resource, ok := results.AllResources[resourceID]; ok {
// jUnitResource.Name = resource.GetName()
// jUnitResource.Namespace = resource.GetNamespace()
// jUnitResource.Kind = resource.GetKind()
// jUnitResource.ApiVersion = resource.GetApiVersion()
// }
// jUnitResources = append(jUnitResources, jUnitResource)
// }
// testCase.Resources = jUnitResources
// juResult.Suites = append(juResult.Suites, testCase)
// }
// return &juResult, nil
// }

View File

@@ -1,41 +0,0 @@
package resourcemapping
import (
"encoding/json"
"fmt"
"os"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/resultshandling/printer"
)
type JsonPrinter struct {
writer *os.File
}
func NewJsonPrinter() *JsonPrinter {
return &JsonPrinter{}
}
func (jsonPrinter *JsonPrinter) SetWriter(outputFile string) {
jsonPrinter.writer = printer.GetWriter(outputFile)
}
func (jsonPrinter *JsonPrinter) Score(score float32) {
fmt.Fprintf(os.Stderr, "\nOverall risk-score (0- Excellent, 100- All failed): %d\n", int(score))
}
func (jsonPrinter *JsonPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
postureReportStr, err := json.Marshal(opaSessionObj.Report)
if err != nil {
logger.L().Fatal("failed to convert posture report object")
}
jsonPrinter.writer.Write(postureReportStr)
}
func (jsonPrinter *JsonPrinter) FinalizeData(opaSessionObj *cautils.OPASessionObj) {
// finalizeReport(opaSessionObj)
}

View File

@@ -2,6 +2,7 @@ package v2
import (
"fmt"
"sort"
"strings"
"github.com/armosec/k8s-interface/workloadinterface"
@@ -21,7 +22,7 @@ func (prettyPrinter *PrettyPrinter) resourceTable(results map[string]resourcesre
summaryTable.SetRowLine(true)
// summaryTable.SetFooter([]string{"", "", "Total", "", "$146.93"})
// For control scan framework will be nil
data := [][]string{}
data := Matrix{}
for i := range results {
resource, ok := allResources[i]
if !ok {
@@ -32,7 +33,7 @@ func (prettyPrinter *PrettyPrinter) resourceTable(results map[string]resourcesre
data = append(data, raw...)
}
}
sortTable(data)
sort.Sort(data)
summaryTable.AppendBulk(data)
summaryTable.Render()
@@ -54,18 +55,8 @@ func generateResourceRows(resource workloadinterface.IMetadata, controls []resou
row = append(row, fmt.Sprintf("%s\nhttps://hub.armo.cloud/docs/%s", controls[i].GetName(), strings.ToLower(controls[i].GetID())))
row = append(row, resource.GetNamespace())
var paths []string
for j := range controls[i].ResourceAssociatedRules {
for k := range controls[i].ResourceAssociatedRules[j].Paths {
if p := controls[i].ResourceAssociatedRules[j].Paths[k].FailedPath; p != "" {
paths = append(paths, p)
}
if p := controls[i].ResourceAssociatedRules[j].Paths[k].FixPath.Path; p != "" {
v := controls[i].ResourceAssociatedRules[j].Paths[k].FixPath.Value
paths = append(paths, fmt.Sprintf("%s=%s", p, v))
}
}
}
paths := failedPathsToString(&controls[i])
row = append(row, fmt.Sprintf("%s/%s\n%s", resource.GetKind(), resource.GetName(), strings.Join(paths, ";\n")))
row = append(row, string(controls[i].GetStatus(nil).Status()))
rows = append(rows, row)
@@ -78,18 +69,35 @@ func generateResourceHeader() []string {
return []string{"Control", "Namespace", "Kind/Name", "Statues"}
}
func sortTable(data [][]string) {
type Matrix [][]string
for j := len(data[0]) - 1; j >= 0; j-- {
for k := 0; k < len(data)-2; {
if data[k][j] > data[k+1][j] {
tmp := data[k]
data[k] = data[k+1]
data[k+1] = tmp
k = 0
} else {
k++
func (a Matrix) Len() int { return len(a) }
func (a Matrix) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a Matrix) Less(i, j int) bool {
l := len(a[i])
for k := 0; k < l; k++ {
if a[i][k] < a[j][k] {
return true
} else if a[i][k] > a[j][k] {
return false
}
}
return true
}
func failedPathsToString(control *resourcesresults.ResourceAssociatedControl) []string {
var paths []string
for j := range control.ResourceAssociatedRules {
for k := range control.ResourceAssociatedRules[j].Paths {
if p := control.ResourceAssociatedRules[j].Paths[k].FailedPath; p != "" {
paths = append(paths, p)
}
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))
}
}
}
return paths
}

View File

@@ -59,7 +59,7 @@ func NewPrinter(printFormat, outputVersion string, verboseMode bool) printer.IPr
case printer.JsonFormat:
return printerv1.NewJsonPrinter()
case printer.JunitResultFormat:
return printerv1.NewJunitPrinter()
return printerv2.NewJunitPrinter(verboseMode)
case printer.PrometheusFormat:
return printerv1.NewPrometheusPrinter(verboseMode)
case printer.PdfFormat: