mirror of
https://github.com/kubescape/kubescape.git
synced 2026-04-15 06:58:11 +00:00
Update junit support
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
package v1
|
||||
|
||||
var INDENT = " "
|
||||
@@ -1,11 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
)
|
||||
|
||||
type SilentPrinter struct {
|
||||
}
|
||||
|
||||
func (silentPrinter *SilentPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -5,18 +5,61 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"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"
|
||||
"github.com/armosec/opa-utils/reporthandling/results/v1/reportsummary"
|
||||
)
|
||||
|
||||
type JunitPrinter struct {
|
||||
writer *os.File
|
||||
writer *os.File
|
||||
verbose bool
|
||||
}
|
||||
|
||||
func NewJunitPrinter() *JunitPrinter {
|
||||
return &JunitPrinter{}
|
||||
type JUnitTestSuites struct {
|
||||
XMLName xml.Name `xml:"testsuite"`
|
||||
Suites []JUnitTestCase `xml:"testsuites"`
|
||||
Frameworks []JUnitFrameworks `xml:"framework"`
|
||||
RiskScore float32 `xml:"riskScore,attr"` // test risk score
|
||||
Time string `xml:"time,attr"` // scanning time
|
||||
Controls int `xml:"testcases,attr"` // number of controls
|
||||
}
|
||||
|
||||
type JUnitFrameworks struct { // Frameworks
|
||||
Name string `xml:"name,attr"`
|
||||
RiskScore float32 `xml:"riskscore,attr"`
|
||||
Status string `xml:"status,attr"`
|
||||
}
|
||||
|
||||
// JUnitTestCase is a single test case with its result.
|
||||
type JUnitTestCase struct { // Control
|
||||
XMLName xml.Name `xml:"testcase"`
|
||||
Name string `xml:"name,attr"`
|
||||
ID string `xml:"id,attr"`
|
||||
Url string `xml:"url,attr"`
|
||||
RiskScore float32 `xml:"riskScore,attr"`
|
||||
Status string `xml:"status,attr"`
|
||||
Info string `xml:"info,attr"`
|
||||
AllResources int `xml:"allResources,attr"`
|
||||
Excluded int `xml:"excludedResources,attr"`
|
||||
Failed int `xml:"filedResources,attr"`
|
||||
Resources []JUnitResource `xml:"resource"`
|
||||
}
|
||||
|
||||
type JUnitResource struct { // Single resource
|
||||
Name string `xml:"name,attr"`
|
||||
Namespace string `xml:"namespace,attr"`
|
||||
Kind string `xml:"kind,attr"`
|
||||
ApiVersion string `xml:"apiVersion,attr"`
|
||||
FailedPaths []armotypes.PosturePaths `xml:"jsonpath"`
|
||||
}
|
||||
|
||||
func NewJunitPrinter(verbose bool) *JunitPrinter {
|
||||
return &JunitPrinter{
|
||||
verbose: verbose,
|
||||
}
|
||||
}
|
||||
|
||||
func (junitPrinter *JunitPrinter) SetWriter(outputFile string) {
|
||||
@@ -32,97 +75,82 @@ func (junitPrinter *JunitPrinter) FinalizeData(opaSessionObj *cautils.OPASession
|
||||
}
|
||||
|
||||
func (junitPrinter *JunitPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
|
||||
junitResult, err := convertPostureReportToJunitResult(opaSessionObj.Report)
|
||||
junitResult, err := junitPrinter.convertPostureReportToJunitResult(opaSessionObj)
|
||||
if err != nil {
|
||||
logger.L().Fatal("failed to convert posture report object")
|
||||
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 convert posture report object")
|
||||
logger.L().Fatal("failed to Marshal xml result object", helpers.Error(err))
|
||||
}
|
||||
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 *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 (junitPrinter *JunitPrinter) convertPostureReportToJunitResult(results *cautils.OPASessionObj) (*JUnitTestSuites, error) {
|
||||
juResult := JUnitTestSuites{
|
||||
XMLName: xml.Name{
|
||||
Local: "Kubescape scan results",
|
||||
},
|
||||
RiskScore: results.Report.SummaryDetails.Score,
|
||||
Time: results.Report.GetTimestamp().String(),
|
||||
Controls: len(results.Report.ListControls().All()),
|
||||
Frameworks: []JUnitFrameworks{},
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// controls
|
||||
for _, controlIDs := range results.Report.ListControlsIDs().All() {
|
||||
controlReport := results.Report.SummaryDetails.Controls.GetControl(reportsummary.EControlCriteriaID, controlIDs)
|
||||
|
||||
// control data
|
||||
testCase := JUnitTestCase{}
|
||||
testCase.Name = controlReport.GetName()
|
||||
testCase.ID = 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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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()
|
||||
@@ -78,18 +79,18 @@ 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
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user