Update format and output flags (#43)

* support stdin input

* support output to file
This commit is contained in:
David Wertenteil
2021-08-30 18:49:17 +03:00
committed by GitHub
parent c8068a8d90
commit 00314be32a
6 changed files with 177 additions and 25 deletions

View File

@@ -28,7 +28,6 @@ If you wish to scan all namespaces in your cluster, remove the `--exclude-namesp
<img src="docs/summary.png">
## Usage & Examples
### Pre-Deployment Testing
@@ -40,8 +39,8 @@ kubescape scan framework nsa *.yaml
### Integration with other tools
Kubescape can produce output fitting for later processing:
* JSON (`-o json`)
* JUnit XML (`-o junit`)
* JSON (`-f json`)
* JUnit XML (`-f junit`)
### Examples
@@ -63,12 +62,12 @@ kubescape scan framework nsa https://raw.githubusercontent.com/GoogleCloudPlatfo
* Output in `json` format
```
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public --silence -o json > results.json
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public --format json --output results.json
```
* Output in `junit xml` format
```
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public --silence -o junit > results.xml
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public --format junit --output results.xml
```
### Helm Support

View File

@@ -1,6 +1,7 @@
package opapolicy
import (
"path/filepath"
"time"
armotypes "kubescape/cautils/armotypes"
@@ -151,8 +152,40 @@ type PolicyIdentifier struct {
type ScanInfo struct {
PolicyIdentifier PolicyIdentifier
Format string
Output string
ExcludedNamespaces string
InputPatterns []string
Silent bool
}
func (scanInfo *ScanInfo) Init() {
scanInfo.setSilentMode()
scanInfo.setOutputFile()
}
func (scanInfo *ScanInfo) setSilentMode() {
if scanInfo.Format == "json" || scanInfo.Format == "junit" {
scanInfo.Silent = true
}
if scanInfo.Output != "" {
scanInfo.Silent = true
}
}
func (scanInfo *ScanInfo) setOutputFile() {
if scanInfo.Output == "" {
return
}
if scanInfo.Format == "json" {
if filepath.Ext(scanInfo.Output) != "json" {
scanInfo.Output += ".json"
}
}
if scanInfo.Format == "junit" {
if filepath.Ext(scanInfo.Output) != "xml" {
scanInfo.Output += ".xml"
}
}
}

View File

@@ -59,6 +59,7 @@ var frameworkCmd = &cobra.Command{
}
scanInfo.InputPatterns = []string{tempFile.Name()}
}
scanInfo.Init()
cautils.SetSilentMode(scanInfo.Silent)
CliSetup()
@@ -74,7 +75,8 @@ func init() {
scanCmd.AddCommand(frameworkCmd)
scanInfo = opapolicy.ScanInfo{}
frameworkCmd.Flags().StringVarP(&scanInfo.ExcludedNamespaces, "exclude-namespaces", "e", "", "namespaces to exclude from check")
frameworkCmd.Flags().StringVarP(&scanInfo.Output, "output", "o", "pretty-printer", "output format. supported formats: 'pretty-printer'/'json'/'junit'")
frameworkCmd.Flags().StringVarP(&scanInfo.Format, "format", "f", "pretty-printer", "output format. supported formats: 'pretty-printer'/'json'/'junit'")
frameworkCmd.Flags().StringVarP(&scanInfo.Output, "output", "o", "", "output file. print output to file and not stdout")
frameworkCmd.Flags().BoolVarP(&scanInfo.Silent, "silent", "s", false, "silent progress output")
}
@@ -100,7 +102,7 @@ func CliSetup() error {
reporterObj := opaprocessor.NewOPAProcessor(&processNotification, &reportResults)
reporterObj.ProcessRulesListenner()
}()
p := printer.NewPrinter(&reportResults, scanInfo.Output)
p := printer.NewPrinter(&reportResults, scanInfo.Format, scanInfo.Output)
p.ActionPrint()
return nil

35
docs/new-feature.svg Normal file
View File

@@ -0,0 +1,35 @@
<svg xmlns="http://www.w3.org/2000/svg" width="104" height="20">
<defs>
<linearGradient id="workflow-fill" x1="50%" y1="0%" x2="50%" y2="100%">
<stop stop-color="#444D56" offset="0%"></stop>
<stop stop-color="#24292E" offset="100%"></stop>
</linearGradient>
<linearGradient id="state-fill" x1="50%" y1="0%" x2="50%" y2="100%">
<stop stop-color="#34D058" offset="0%"></stop>
<stop stop-color="#28A745" offset="100%"></stop>
</linearGradient>
</defs>
<g fill="none" fill-rule="evenodd">
<g font-family="&#39;DejaVu Sans&#39;,Verdana,Geneva,sans-serif" font-size="11">
<path id="workflow-bg" d="M0,3 C0,1.3431 1.3552,0 3.02702703,0 L54,0 L54,20 L3.02702703,20 C1.3552,20 0,18.6569 0,17 L0,3 Z" fill="url(#workflow-fill)" fill-rule="nonzero"></path>
<text fill="#010101" fill-opacity=".3">
<tspan x="22.1981982" y="15">new</tspan>
</text>
<text fill="#FFFFFF">
<tspan x="22.1981982" y="14">new</tspan>
</text>
</g>
<g transform="translate(54)" font-family="&#39;DejaVu Sans&#39;,Verdana,Geneva,sans-serif" font-size="11">
<path d="M0 0h46.939C48.629 0 50 1.343 50 3v14c0 1.657-1.37 3-3.061 3H0V0z" id="state-bg" fill="url(#state-fill)" fill-rule="nonzero"></path>
<text fill="#010101" fill-opacity=".3">
<tspan x="4" y="15">feature</tspan>
</text>
<text fill="#FFFFFF">
<tspan x="4" y="14">feature</tspan>
</text>
</g>
<path fill="#959DA5" d="M11 3c-3.868 0-7 3.132-7 7a6.996 6.996 0 0 0 4.786 6.641c.35.062.482-.148.482-.332 0-.166-.01-.718-.01-1.304-1.758.324-2.213-.429-2.353-.822-.079-.202-.42-.823-.717-.99-.245-.13-.595-.454-.01-.463.552-.009.946.508 1.077.718.63 1.058 1.636.76 2.039.577.061-.455.245-.761.446-.936-1.557-.175-3.185-.779-3.185-3.456 0-.762.271-1.392.718-1.882-.07-.175-.315-.892.07-1.855 0 0 .586-.183 1.925.718a6.5 6.5 0 0 1 1.75-.236 6.5 6.5 0 0 1 1.75.236c1.338-.91 1.925-.718 1.925-.718.385.963.14 1.68.07 1.855.446.49.717 1.112.717 1.882 0 2.686-1.636 3.28-3.194 3.456.254.219.473.639.473 1.295 0 .936-.009 1.689-.009 1.925 0 .184.131.402.481.332A7.011 7.011 0 0 0 18 10c0-3.867-3.133-7-7-7z"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

67
docs/release.md Normal file
View File

@@ -0,0 +1,67 @@
# Kubescape Release
## Input
### Scan a running Kubernetes cluster
* Scan your Kubernetes cluster. Ignore `kube-system` and `kube-public` namespaces
```
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public
```
* Scan your Kubernetes cluster
```
kubescape scan framework nsa
```
### Scan a local Kubernetes manifest
* Scan single Kubernetes manifest file <img src="new-feature.svg">
```
kubescape scan framework nsa <my-workload.yaml>
```
* Scan many Kubernetes manifest files <img src="new-feature.svg">
```
kubescape scan framework nsa <my-workload-1.yaml> <my-workload-2.yaml>
```
* Scan all Kubernetes manifest files in directory <img src="new-feature.svg">
```
kubescape scan framework nsa *.yaml
```
* Scan Kubernetes manifest from stdout <img src="new-feature.svg">
```
cat <my-workload.yaml> | kubescape scan framework nsa -
```
* Scan Kubernetes manifest url <img src="new-feature.svg">
```
kubescape scan framework nsa https://raw.githubusercontent.com/GoogleCloudPlatform/microservices-demo/master/release/kubernetes-manifests.yaml
```
### Scan HELM chart
* Render the helm chart using [`helm template`](https://helm.sh/docs/helm/helm_template/) and pass to stdout <img src="new-feature.svg">
```
helm template [CHART] [flags] --generate-name --dry-run | kubescape scan framework nsa -
```
## Output formats
By default, the output is user friendly.
For the sake of automation, it is possible to receive the result in a `json` or `junit xml` format.
* Output in `json` format <img src="new-feature.svg">
```
kubescape scan framework nsa --format json --output results.json
```
* Output in `junit xml` format <img src="new-feature.svg">
```
kubescape scan framework nsa --format junit --output results.xml
```

View File

@@ -27,16 +27,18 @@ const (
type Printer struct {
opaSessionObj *chan *cautils.OPASessionObj
writer *os.File
summary Summary
sortedControlNames []string
printerType string
}
func NewPrinter(opaSessionObj *chan *cautils.OPASessionObj, printerType string) *Printer {
func NewPrinter(opaSessionObj *chan *cautils.OPASessionObj, printerType, outputFile string) *Printer {
return &Printer{
opaSessionObj: opaSessionObj,
summary: NewSummary(),
printerType: printerType,
writer: getWriter(outputFile),
}
}
@@ -54,7 +56,7 @@ func (printer *Printer) ActionPrint() {
fmt.Println("Failed to convert posture report object!")
os.Exit(1)
}
os.Stdout.Write(postureReportStr)
printer.writer.Write(postureReportStr)
} else if printer.printerType == JunitResultPrinter {
junitResult, err := convertPostureReportToJunitResult(opaSessionObj.PostureReport)
if err != nil {
@@ -66,7 +68,7 @@ func (printer *Printer) ActionPrint() {
fmt.Println("Failed to convert posture report object!")
os.Exit(1)
}
os.Stdout.Write(postureReportStr)
printer.writer.Write(postureReportStr)
} else if !cautils.IsSilent() {
fmt.Println("unknown output printer")
os.Exit(1)
@@ -113,29 +115,29 @@ func (printer *Printer) PrintResults() {
}
}
func (print *Printer) printSummary(controlName string, controlSummary *ControlSummary) {
cautils.SimpleDisplay(os.Stdout, "Summary - ")
cautils.SuccessDisplay(os.Stdout, "Passed:%v ", controlSummary.TotalResources-controlSummary.TotalFailed)
cautils.FailureDisplay(os.Stdout, "Failed:%v ", controlSummary.TotalFailed)
cautils.InfoDisplay(os.Stdout, "Total:%v\n", controlSummary.TotalResources)
func (printer *Printer) printSummary(controlName string, controlSummary *ControlSummary) {
cautils.SimpleDisplay(printer.writer, "Summary - ")
cautils.SuccessDisplay(printer.writer, "Passed:%v ", controlSummary.TotalResources-controlSummary.TotalFailed)
cautils.FailureDisplay(printer.writer, "Failed:%v ", controlSummary.TotalFailed)
cautils.InfoDisplay(printer.writer, "Total:%v\n", controlSummary.TotalResources)
if controlSummary.TotalFailed > 0 {
cautils.DescriptionDisplay(os.Stdout, "Remediation: %v\n", controlSummary.Remediation)
cautils.DescriptionDisplay(printer.writer, "Remediation: %v\n", controlSummary.Remediation)
}
cautils.DescriptionDisplay(os.Stdout, "\n")
cautils.DescriptionDisplay(printer.writer, "\n")
}
func (printer *Printer) printTitle(controlName string, controlSummary *ControlSummary) {
cautils.InfoDisplay(os.Stdout, "[control: %s] ", controlName)
cautils.InfoDisplay(printer.writer, "[control: %s] ", controlName)
if controlSummary.TotalResources == 0 {
cautils.InfoDisplay(os.Stdout, "resources not found %v\n", emoji.ConfusedFace)
cautils.InfoDisplay(printer.writer, "resources not found %v\n", emoji.ConfusedFace)
} else if controlSummary.TotalFailed == 0 {
cautils.SuccessDisplay(os.Stdout, "passed %v\n", emoji.ThumbsUp)
cautils.SuccessDisplay(printer.writer, "passed %v\n", emoji.ThumbsUp)
} else {
cautils.FailureDisplay(os.Stdout, "failed %v\n", emoji.SadButRelievedFace)
cautils.FailureDisplay(printer.writer, "failed %v\n", emoji.SadButRelievedFace)
}
cautils.DescriptionDisplay(os.Stdout, "Description: %s\n", controlSummary.Description)
cautils.DescriptionDisplay(printer.writer, "Description: %s\n", controlSummary.Description)
}
func (printer *Printer) printResult(controlName string, controlSummary *ControlSummary) {
@@ -144,12 +146,12 @@ func (printer *Printer) printResult(controlName string, controlSummary *ControlS
for ns, rsc := range controlSummary.WorkloadSummary {
preIndent := indent
if ns != "" {
cautils.SimpleDisplay(os.Stdout, "%sNamespace %s\n", indent, ns)
cautils.SimpleDisplay(printer.writer, "%sNamespace %s\n", indent, ns)
}
preIndent2 := indent
for r := range rsc {
indent += indent
cautils.SimpleDisplay(os.Stdout, fmt.Sprintf("%s%s - %s\n", indent, rsc[r].Kind, rsc[r].Name))
cautils.SimpleDisplay(printer.writer, fmt.Sprintf("%s%s - %s\n", indent, rsc[r].Kind, rsc[r].Name))
indent = preIndent2
}
indent = preIndent
@@ -195,7 +197,7 @@ func generateFooter(numControlers, sumFailed, sumTotal int) []string {
return row
}
func (printer *Printer) PrintSummaryTable() {
summaryTable := tablewriter.NewWriter(os.Stdout)
summaryTable := tablewriter.NewWriter(printer.writer)
summaryTable.SetAutoWrapText(false)
summaryTable.SetHeader(generateHeader())
summaryTable.SetHeaderLine(true)
@@ -221,3 +223,17 @@ func (printer *Printer) getSortedControlsNames() []string {
sort.Strings(controlNames)
return controlNames
}
func getWriter(outputFile string) *os.File {
if outputFile != "" {
f, err := os.OpenFile(outputFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
fmt.Println("Error opening file")
return os.Stdout
}
return f
}
return os.Stdout
}