Skip to content
This repository has been archived by the owner on Oct 1, 2023. It is now read-only.

Commit

Permalink
feat: Display CIS Benchmark results (#18)
Browse files Browse the repository at this point in the history
Signed-off-by: Daniel Pacak <pacak.daniel@gmail.com>
  • Loading branch information
danielpacak authored Apr 3, 2020
1 parent 864215a commit 7237616
Show file tree
Hide file tree
Showing 24 changed files with 594 additions and 132 deletions.
20 changes: 17 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ kubectl get vulnerabilities \
assuming that `kubectl starboard find vulnerabilities pod/nginx` command was run and the vulnerabilities report saved
as a `vulnerabilities.aquasecurity.github.com` resource.

![](./docs/images/pod_vulnerabilities.png)
![](./docs/images/vulnerabilities_pod.png)

### Show vulnerabilities summary

Shows the summary of vulnerabilities found in the Pod within the *Status* card.

![](./docs/images/pod_vulnerabilities_summary.png)
![](./docs/images/vulnerabilities_summary_pod.png)

### List vulnerabilities of [Deployment][k8s-deployment]

Expand All @@ -55,7 +55,20 @@ kubectl get vulns
assuming that `kubectl starboard find vulns deployments/nginx` command was run and the vulnerabilities report saved
as a `vulnerabilities.aquasecurity.github.com` resource.

![](./docs/images/deployment_vulnerabilities.png)
![](./docs/images/vulnerabilities_deploy.png)

### CIS Kubernetes Benchmark checks for [Node][k8s-node]

This is equivalent of

```
k get ciskubebench minikube -o yaml
```

assuming that `kubectl starboard kube-bench` command was run and the output of running CIS Kubernetes Benchmarks checks
saved as a `ciskubernetesbenchmarks.aquasecurity.github.com` resource.

![](./docs/images/cis_kubernetes_benchmark_node.png)

## Uninstall

Expand All @@ -81,3 +94,4 @@ This repository is available under the [Apache License 2.0][license].
[k8s-security-crds]: https://github.com/aquasecurity/k8s-security-crds
[k8s-pod]: https://kubernetes.io/docs/concepts/workloads/pods/pod/
[k8s-deployment]: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/
[k8s-node]: https://kubernetes.io/docs/concepts/architecture/nodes/
44 changes: 21 additions & 23 deletions cmd/octant-starboard-plugin/main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"errors"
"fmt"
"log"
"os"
Expand All @@ -10,7 +11,6 @@ import (
security "github.com/aquasecurity/k8s-security-crds/pkg/apis/aquasecurity/v1alpha1"
"github.com/aquasecurity/octant-starboard-plugin/pkg/data"
"github.com/aquasecurity/octant-starboard-plugin/pkg/view"
"github.com/pkg/errors"
"github.com/vmware-tanzu/octant/pkg/plugin"
"github.com/vmware-tanzu/octant/pkg/plugin/service"
"github.com/vmware-tanzu/octant/pkg/view/component"
Expand All @@ -24,6 +24,7 @@ const (
)

var (
nodeGVK = schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Node"}
podGVK = schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"}
deploymentGVK = schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}
daemonSetGVK = schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "DaemonSet"}
Expand All @@ -40,7 +41,7 @@ func run(_ []string) (err error) {
log.SetPrefix("")

capabilities := &plugin.Capabilities{
SupportsTab: []schema.GroupVersionKind{podGVK, deploymentGVK, daemonSetGVK, namespaceGVK},
SupportsTab: []schema.GroupVersionKind{podGVK, deploymentGVK, daemonSetGVK, namespaceGVK, nodeGVK},
SupportsPrinterConfig: []schema.GroupVersionKind{podGVK, deploymentGVK, daemonSetGVK},
IsModule: false,
}
Expand Down Expand Up @@ -78,52 +79,53 @@ func handleVulnerabilitiesTab(request *service.PrintRequest) (tag plugin.TabResp
return handleVulnerabilitiesTabForWorkload(request, data.Workload{Kind: kind, Name: name})
case data.KindNamespace:
return handleVulnerabilitiesTabForNamespace(request, name)
case data.KindNode:
return handleCISBenchmarkTabForNode(request, name)
}

return
}

func handleVulnerabilitiesTabForWorkload(request *service.PrintRequest, workload data.Workload) (tab plugin.TabResponse, err error) {
func handleVulnerabilitiesTabForWorkload(request *service.PrintRequest, workload data.Workload) (tabResponse plugin.TabResponse, err error) {
repository := data.NewRepository(request.DashboardClient)
reports, err := repository.GetVulnerabilitiesForWorkload(request.Context(), workload)
if err != nil {
return
}

tab = plugin.TabResponse{Tab: createVulnerabilitiesTab(reports)}
tab := component.NewTabWithContents(view.NewVulnerabilitiesReport(reports))
tabResponse = plugin.TabResponse{Tab: tab}

return
}

func handleVulnerabilitiesTabForNamespace(request *service.PrintRequest, namespace string) (tab plugin.TabResponse, err error) {
func handleVulnerabilitiesTabForNamespace(request *service.PrintRequest, namespace string) (tabResponse plugin.TabResponse, err error) {
repository := data.NewRepository(request.DashboardClient)
reports, err := repository.GetVulnerabilitiesForNamespace(request.Context(), namespace)
if err != nil {
return
}
tab = plugin.TabResponse{Tab: createVulnerabilitiesTab([]data.ContainerImageScanReport{reports})}
tab := component.NewTabWithContents(view.NewVulnerabilitiesReport([]data.ContainerImageScanReport{reports}))
tabResponse = plugin.TabResponse{Tab: tab}
return
}

func createVulnerabilitiesTab(reports []data.ContainerImageScanReport) *component.Tab {
flexLayout := component.NewFlexLayout("Vulnerabilities")
var items []component.FlexLayoutItem
for _, containerReport := range reports {
items = append(items, component.FlexLayoutItem{
Width: component.WidthFull,
View: view.NewImageScanReport(containerReport.Name, containerReport.Report),
})
func handleCISBenchmarkTabForNode(request *service.PrintRequest, node string) (tabResponse plugin.TabResponse, err error) {
repository := data.NewRepository(request.DashboardClient)
report, err := repository.GetCISKubernetesBenchmark(request.Context(), node)
if err != nil {
return
}

flexLayout.AddSections(items)

return component.NewTabWithContents(*flexLayout)
tab := component.NewTabWithContents(view.NewCISKubernetesBenchmarksReport(report))
tabResponse = plugin.TabResponse{Tab: tab}
return
}

// handlePrinterConfig is called when Octant wants to print an object.
func handlePrinterConfig(request *service.PrintRequest) (plugin.PrintResponse, error) {
if request.Object == nil {
return plugin.PrintResponse{}, errors.Errorf("object is nil")
return plugin.PrintResponse{}, errors.New("object is nil")
}

repository := data.NewRepository(request.DashboardClient)
Expand Down Expand Up @@ -153,13 +155,9 @@ func handlePrinterConfig(request *service.PrintRequest) (plugin.PrintResponse, e
return plugin.PrintResponse{}, err
}

vs := component.NewSummary("Vulnerabilities",
summarySectionsFor(summary)...,
)

printItems = append(printItems, component.FlexLayoutItem{
Width: component.WidthHalf,
View: vs,
View: view.NewVulnerabilitiesSummary("Vulnerabilities", summary),
})

// When printing an object, you can create multiple types of content. In this
Expand Down
Binary file added docs/images/cis_kubernetes_benchmark_node.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed docs/images/deployment_vulnerabilities.png
Binary file not shown.
Binary file removed docs/images/pod_vulnerabilities.png
Binary file not shown.
Binary file removed docs/images/pod_vulnerabilities_summary.png
Binary file not shown.
Binary file added docs/images/vulnerabilities_deploy.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/vulnerabilities_pod.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/vulnerabilities_summary_pod.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 2 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ module github.com/aquasecurity/octant-starboard-plugin
go 1.13

require (
github.com/aquasecurity/k8s-security-crds v0.0.12
github.com/pkg/errors v0.8.1
github.com/vmware-tanzu/octant v0.11.0
github.com/aquasecurity/k8s-security-crds v0.0.13
github.com/vmware-tanzu/octant v0.11.1
k8s.io/apimachinery v0.17.0
)
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/aquasecurity/k8s-security-crds v0.0.12 h1:5iOvnSXRZEcqbAT9KYKHCdhA1VWH3Bla/6JOyjAWlro=
github.com/aquasecurity/k8s-security-crds v0.0.12/go.mod h1:G7RQ8h2b2tkcGZJHBong/aROdZLMJtSGxwB7BF1s7Xk=
github.com/aquasecurity/k8s-security-crds v0.0.13 h1:oQaGqBtlt2uZpjwJXNe7cWe+5CxHT9chq+I1RoHc+GM=
github.com/aquasecurity/k8s-security-crds v0.0.13/go.mod h1:G7RQ8h2b2tkcGZJHBong/aROdZLMJtSGxwB7BF1s7Xk=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
Expand Down Expand Up @@ -368,8 +368,8 @@ github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijb
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
github.com/vmware-tanzu/octant v0.11.0 h1:1ganDAIiswCL2QDu/C0pczWoraSMIWMtq79GOCqnrr8=
github.com/vmware-tanzu/octant v0.11.0/go.mod h1:ree4Qu6ULumg1MlGrAG7avEQx3+NjWPc0Zs4nQWOmQE=
github.com/vmware-tanzu/octant v0.11.1 h1:04qHGWp/pHcJj/3MahuXpM2ZvjbVVc4YK1o1kbwtgCU=
github.com/vmware-tanzu/octant v0.11.1/go.mod h1:ree4Qu6ULumg1MlGrAG7avEQx3+NjWPc0Zs4nQWOmQE=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
Expand Down
44 changes: 39 additions & 5 deletions pkg/data/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const (
WorkloadKindDeployment = "Deployment"
WorkloadKindDaemonSet = "DaemonSet"
KindNamespace = "Namespace"
KindNode = "Node"
)

const (
Expand All @@ -26,8 +27,9 @@ const (
)

const (
vulnerabilitiesAPIVersion = "aquasecurity.github.com/v1alpha1"
vulnerabilitiesKind = "Vulnerability"
aquaSecurityAPIVersion = "aquasecurity.github.com/v1alpha1"
vulnerabilitiesKind = "Vulnerability"
CISKubernetesBenchmarkKind = "CISKubernetesBenchmark"
)

type Workload struct {
Expand Down Expand Up @@ -76,7 +78,7 @@ func (r *Repository) GetVulnerabilitiesSummary(ctx context.Context, options Work

func (r *Repository) GetVulnerabilitiesForNamespace(ctx context.Context, namespace string) (report ContainerImageScanReport, err error) {
unstructuredList, err := r.client.List(ctx, store.Key{
APIVersion: vulnerabilitiesAPIVersion,
APIVersion: aquaSecurityAPIVersion,
Kind: vulnerabilitiesKind,
Namespace: namespace,
})
Expand Down Expand Up @@ -120,7 +122,7 @@ func (r *Repository) GetVulnerabilitiesForNamespace(ctx context.Context, namespa

func (r *Repository) GetVulnerabilitiesForWorkload(ctx context.Context, options Workload) (reports []ContainerImageScanReport, err error) {
unstructuredList, err := r.client.List(ctx, store.Key{
APIVersion: vulnerabilitiesAPIVersion,
APIVersion: aquaSecurityAPIVersion,
Kind: vulnerabilitiesKind,
// TODO Report bug to Octant? Apparently the label selector doesn't work and I have to do filtering manually :(
//Selector: &labels.Set{
Expand All @@ -140,7 +142,7 @@ func (r *Repository) GetVulnerabilitiesForWorkload(ctx context.Context, options
var reportList security.VulnerabilityList
err = json.Unmarshal(b, &reportList)
if err != nil {
err = fmt.Errorf("unmarshalling JSON to ImageScanReport: %w", err)
err = fmt.Errorf("unmarshalling JSON to VulnerabilityList: %w", err)
return
}
for _, item := range reportList.Items {
Expand All @@ -161,3 +163,35 @@ func (r *Repository) GetVulnerabilitiesForWorkload(ctx context.Context, options

return
}

func (r *Repository) GetCISKubernetesBenchmark(ctx context.Context, node string) (report security.CISKubernetesBenchmark, err error) {
unstructuredList, err := r.client.List(ctx, store.Key{
APIVersion: aquaSecurityAPIVersion,
Kind: CISKubernetesBenchmarkKind,
Name: node,
})
if err != nil {
err = fmt.Errorf("listing CIS Kubernetes Benchmarks: %w", err)
return
}
b, err := unstructuredList.MarshalJSON()
if err != nil {
err = fmt.Errorf("marshalling unstructured list to JSON: %w", err)
return
}
var reportList security.CISKubernetesBenchmarkList
err = json.Unmarshal(b, &reportList)
if err != nil {
err = fmt.Errorf("unmarshalling JSON to CISKubernetesBenchmarkList: %w", err)
return
}

for _, r := range reportList.Items {
if r.Name == node {
report = r
return
}
}

return
}
74 changes: 74 additions & 0 deletions pkg/view/cis_benchmarks_report.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package view

import (
"fmt"
"strconv"

sec "github.com/aquasecurity/k8s-security-crds/pkg/apis/aquasecurity/v1alpha1"
"github.com/vmware-tanzu/octant/pkg/view/component"
)

func NewCISKubernetesBenchmarksReport(benchmark sec.CISKubernetesBenchmark) (flexLayout component.FlexLayout) {
flexLayout = *component.NewFlexLayout("CIS Kubernetes Benchmark")
uiSections := make([]component.FlexLayoutItem, len(benchmark.Report.Sections))

for i, section := range benchmark.Report.Sections {
uiSections[i] = component.FlexLayoutItem{
Width: component.WidthFull,
View: createTableForSection(section),
}
}

uiSections = append([]component.FlexLayoutItem{
{
Width: component.WidthThird,
View: NewReportSummary(benchmark.Report.GeneratedAt.Time),
},
{
Width: component.WidthThird,
View: NewScannerSummary(benchmark.Report.Scanner),
},
{
Width: component.WidthThird,
View: NewCISKubernetesBenchmarksSummary(benchmark),
},
}, uiSections...)

flexLayout.AddSections(uiSections)
return
}

func createTableForSection(section sec.CISKubernetesBenchmarkSection) component.Component {
table := component.NewTableWithRows(
fmt.Sprintf("%s %s", section.ID, section.Text), "There are no results!",
component.NewTableCols("Status", "Number", "Description", "Scored"),
[]component.TableRow{})

for _, test := range section.Tests {
for _, result := range test.Results {

tr := component.TableRow{
"Status": component.NewText(result.Status),
"Number": component.NewText(result.TestNumber),
"Description": component.NewText(result.TestDesc),
"Scored": component.NewText(strconv.FormatBool(result.Scored)),
}
table.Add(tr)
}
}

return table
}

// TODO Implement summary counting
func NewCISKubernetesBenchmarksSummary(_ sec.CISKubernetesBenchmark) (c *component.Summary) {
c = component.NewSummary("Summary")

sections := []component.SummarySection{
{Header: "PASS ", Content: component.NewText(strconv.Itoa(30))},
{Header: "WARN ", Content: component.NewText(strconv.Itoa(10))},
{Header: "FAIL ", Content: component.NewText(strconv.Itoa(12))},
}
c.Add(sections...)
return
}
37 changes: 0 additions & 37 deletions pkg/view/image_scan_report.go

This file was deleted.

Loading

0 comments on commit 7237616

Please sign in to comment.