Merge remote-tracking branch 'upstream/dev'

This commit is contained in:
danielgrunbergerarmo
2021-08-26 11:17:37 +03:00
5 changed files with 132 additions and 18 deletions

View File

@@ -4,7 +4,7 @@
[![Github All Releases](https://img.shields.io/github/downloads/armosec/kubescape/total.svg)]()
[![Go Report Card](https://goreportcard.com/badge/github.com/armosec/kubescape)](https://goreportcard.com/report/github.com/armosec/kubescape)
Kubescape is the first tool for testing if Kubernetes is deployed securely as defined in [Kubernetes Hardening Guidance by to NSA and CISA](https://www.nsa.gov/News-Features/Feature-Stories/Article-View/Article/2716980/nsa-cisa-release-kubernetes-hardening-guidance/)
Kubescape is the first tool for testing if Kubernetes is deployed securely as defined in [Kubernetes Hardening Guidance by NSA and CISA](https://www.nsa.gov/News-Features/Feature-Stories/Article-View/Article/2716980/nsa-cisa-release-kubernetes-hardening-guidance/)
Tests are configured with YAML files, making this tool easy to update as test specifications evolve.
<img src="docs/demo.gif">
@@ -28,10 +28,12 @@ kubescape scan framework nsa --exclude-namespaces kube-system,kube-public
If you wish to scan all namespaces in your cluster, remove the `--exclude-namespaces` flag.
<img src="docs/summery.PNG">
<img src="docs/summary.png">
# How to build
Note: development (and the release process) is done with Go 1.16
1. Clone Project
```
git clone git@github.com:armosec/kubescape.git kubescape && cd "$_"
@@ -52,7 +54,7 @@ go mod tidy && go build -o kubescape .
# Under the hood
## Tests
Kubescape is running the following tests according to what is defined by [Kubernetes Hardening Guidance by to NSA and CISA](https://www.nsa.gov/News-Features/Feature-Stories/Article-View/Article/2716980/nsa-cisa-release-kubernetes-hardening-guidance/)
Kubescape is running the following tests according to what is defined by [Kubernetes Hardening Guidance by NSA and CISA](https://www.nsa.gov/News-Features/Feature-Stories/Article-View/Article/2716980/nsa-cisa-release-kubernetes-hardening-guidance/)
* Non-root containers
* Immutable container filesystem
* Privileged containers

View File

Before

Width:  |  Height:  |  Size: 206 KiB

After

Width:  |  Height:  |  Size: 206 KiB

78
printer/junit.go Normal file
View File

@@ -0,0 +1,78 @@
package printer
import (
"encoding/xml"
"fmt"
"kube-escape/cautils/opapolicy"
)
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"`
Failures int `xml:"failures,attr"`
Time string `xml:"time,attr"`
Name string `xml:"name,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"`
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 *opapolicy.PostureReport) (*JUnitTestSuites, error) {
juResult := JUnitTestSuites{XMLName: xml.Name{Local: "Kubescape scan results"}}
for _, framework := range postureResult.FrameworkReports {
suite := JUnitTestSuite{Name: framework.Name}
for _, controlReports := range framework.ControlReports {
suite.Tests = suite.Tests + 1
testCase := JUnitTestCase{}
testCase.Name = controlReports.Name
testCase.Classname = "Kubescape"
testCase.Time = "0"
if 0 < len(controlReports.RuleReports[0].RuleResponses) {
suite.Failures = suite.Failures + 1
failure := JUnitFailure{}
failure.Message = fmt.Sprintf("%d resources failed", len(controlReports.RuleReports[0].RuleResponses))
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,6 +1,8 @@
package printer
import (
"encoding/json"
"encoding/xml"
"fmt"
"kube-escape/cautils"
"os"
@@ -15,16 +17,24 @@ import (
var INDENT = " "
const (
PrettyPrinter string = "pretty-printer"
JsonPrinter string = "json-printer"
JunitResultPrinter string = "junit-result-printer"
)
type Printer struct {
opaSessionObj *chan *cautils.OPASessionObj
summery Summery
summary Summary
sortedControlNames []string
printerType string
}
func NewPrinter(opaSessionObj *chan *cautils.OPASessionObj) *Printer {
func NewPrinter(opaSessionObj *chan *cautils.OPASessionObj, printerType string) *Printer {
return &Printer{
opaSessionObj: opaSessionObj,
summery: NewSummery(),
summary: NewSummery(),
printerType: printerType,
}
}
@@ -33,9 +43,33 @@ func (printer *Printer) ActionPrint() {
for {
opaSessionObj := <-*printer.opaSessionObj
printer.SummerySetup(opaSessionObj.PostureReport)
printer.PrintResults()
printer.PrintSummaryTable()
if printer.printerType == PrettyPrinter {
printer.SummerySetup(opaSessionObj.PostureReport)
printer.PrintResults()
printer.PrintSummaryTable()
} else if printer.printerType == JsonPrinter {
postureReportStr, err := json.Marshal(opaSessionObj.PostureReport.FrameworkReports[0])
if err != nil {
fmt.Println("Failed to convert posture report object!")
os.Exit(1)
}
os.Stdout.Write(postureReportStr)
} else if printer.printerType == JunitResultPrinter {
junitResult, err := convertPostureReportToJunitResult(opaSessionObj.PostureReport)
if err != nil {
fmt.Println("Failed to convert posture report object!")
os.Exit(1)
}
postureReportStr, err := xml.Marshal(junitResult)
if err != nil {
fmt.Println("Failed to convert posture report object!")
os.Exit(1)
}
os.Stdout.Write(postureReportStr)
} else {
fmt.Println("unknown output printer")
os.Exit(1)
}
if !k8sinterface.RunningIncluster {
break
@@ -52,7 +86,7 @@ func (printer *Printer) SummerySetup(postureReport *opapolicy.PostureReport) {
workloadsSummery := listResultSummery(cr.RuleReports)
mapResources := groupByNamespace(workloadsSummery)
printer.summery[cr.Name] = ControlSummery{
printer.summary[cr.Name] = ControlSummery{
TotalResources: cr.GetNumberOfResources(),
TotalFailed: len(workloadsSummery),
WorkloadSummery: mapResources,
@@ -67,11 +101,11 @@ func (printer *Printer) SummerySetup(postureReport *opapolicy.PostureReport) {
func (printer *Printer) PrintResults() {
for i := 0; i < len(printer.sortedControlNames); i++ {
controlSummery := printer.summery[printer.sortedControlNames[i]]
controlSummery := printer.summary[printer.sortedControlNames[i]]
printer.printTitle(printer.sortedControlNames[i], &controlSummery)
printer.printResult(printer.sortedControlNames[i], &controlSummery)
if printer.summery[printer.sortedControlNames[i]].TotalResources > 0 {
if printer.summary[printer.sortedControlNames[i]].TotalResources > 0 {
printer.printSummery(printer.sortedControlNames[i], &controlSummery)
}
@@ -161,18 +195,18 @@ func (printer *Printer) PrintSummaryTable() {
sumFailed := 0
for i := 0; i < len(printer.sortedControlNames); i++ {
controlSummery := printer.summery[printer.sortedControlNames[i]]
controlSummery := printer.summary[printer.sortedControlNames[i]]
summaryTable.Append(generateRow(printer.sortedControlNames[i], controlSummery))
sumFailed += controlSummery.TotalFailed
sumTotal += controlSummery.TotalResources
}
summaryTable.SetFooter(generateFooter(len(printer.summery), sumFailed, sumTotal))
summaryTable.SetFooter(generateFooter(len(printer.summary), sumFailed, sumTotal))
summaryTable.Render()
}
func (printer *Printer) getSortedControlsNames() []string {
controlNames := make([]string, 0, len(printer.summery))
for k := range printer.summery {
controlNames := make([]string, 0, len(printer.summary))
for k := range printer.summary {
controlNames = append(controlNames, k)
}
sort.Strings(controlNames)

View File

@@ -4,9 +4,9 @@ import (
"fmt"
)
type Summery map[string]ControlSummery
type Summary map[string]ControlSummery
func NewSummery() Summery {
func NewSummery() Summary {
return make(map[string]ControlSummery)
}