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

Commit

Permalink
feat: Display all vulnerabilities found in a given namespace (#13)
Browse files Browse the repository at this point in the history
Resolves: #12

Signed-off-by: Daniel Pacak <pacak.daniel@gmail.com>
  • Loading branch information
danielpacak authored Mar 10, 2020
1 parent 70dfa90 commit 87787cc
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 50 deletions.
66 changes: 51 additions & 15 deletions cmd/octant-risky-plugin/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,35 +12,53 @@ import (
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime/schema"
"log"
"os"
"strconv"
"time"
)

const (
pluginName = "risky"
pluginDescription = "Kubernetes-native risk explorer plugin"
)

var (
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"}
namespaceGVK = schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Namespace"}
)

func main() {
log.SetPrefix("")
if err := run(os.Args); err != nil {
log.Fatalf("error: %v", err)
}
}

podGVK := schema.GroupVersionKind{Version: "v1", Kind: "Pod"}
deploymentGVK := schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}
func run(_ []string) (err error) {
log.SetPrefix("")

capabilities := &plugin.Capabilities{
SupportsTab: []schema.GroupVersionKind{podGVK, deploymentGVK},
SupportsPrinterConfig: []schema.GroupVersionKind{podGVK, deploymentGVK},
SupportsTab: []schema.GroupVersionKind{podGVK, deploymentGVK, daemonSetGVK, namespaceGVK},
SupportsPrinterConfig: []schema.GroupVersionKind{podGVK, deploymentGVK, daemonSetGVK},
IsModule: false,
}
options := []service.PluginOption{
service.WithTabPrinter(handleTab),
service.WithTabPrinter(handleVulnerabilitiesTab),
service.WithPrinter(handlePrinterConfig),
}
p, err := service.Register("risky", "Kubernetes-native risk explorer plugin", capabilities, options...)
p, err := service.Register(pluginName, pluginDescription, capabilities, options...)
if err != nil {
log.Fatal(err)
return
}
p.Serve()
return
}

func handleTab(request *service.PrintRequest) (tab plugin.TabResponse, err error) {
// handleVulnerabilitiesTab is called when Octant want to print the Vulnerabilities tab.
func handleVulnerabilitiesTab(request *service.PrintRequest) (tag plugin.TabResponse, err error) {
if request.Object == nil {
err = errors.New("object is nil")
err = errors.New("request object is nil")
return
}

Expand All @@ -54,20 +72,38 @@ func handleTab(request *service.PrintRequest) (tab plugin.TabResponse, err error
return
}

switch kind {
case data.WorkloadKindPod, data.WorkloadKindDeployment, data.WorkloadKindDaemonSet:
return handleVulnerabilitiesTabForWorkload(request, data.Workload{Kind: kind, Name: name})
case data.KindNamespace:
return handleVulnerabilitiesTabForNamespace(request, name)
}

return
}

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

tab = plugin.TabResponse{Tab: createVulnerabilitiesTab(reports)}

return
}

func handleVulnerabilitiesTabForNamespace(request *service.PrintRequest, namespace string) (tab 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})}
return
}

func createVulnerabilitiesTab(reports []data.ContainerImageScanReport) *component.Tab {
flexLayout := component.NewFlexLayout("Vulnerabilities")
var items []component.FlexLayoutItem
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/aquasecurity/k8s-security-crds v0.0.7
github.com/pkg/errors v0.8.1
github.com/vmware-tanzu/octant v0.10.2
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7
k8s.io/api v0.17.3
k8s.io/apimachinery v0.17.3
)
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,7 @@ golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgw
golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
Expand Down
113 changes: 80 additions & 33 deletions pkg/data/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,33 @@ package data
import (
"context"
"encoding/json"
"fmt"
security "github.com/aquasecurity/k8s-security-crds/pkg/apis/security/v1alpha1"
"github.com/vmware-tanzu/octant/pkg/plugin/service"
"github.com/vmware-tanzu/octant/pkg/store"
core "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"golang.org/x/xerrors"
"sort"
"strings"
)

const (
WorkloadKindPod = "Pod"
WorkloadKindDeployment = "Deployment"
WorkloadKindDaemonSet = "DaemonSet"
KindNamespace = "Namespace"
)

const (
labelWorkloadKind = "risky.workload.kind"
labelWorkloadName = "risky.workload.name"
labelContainerName = "risky.container.name"
)

const (
vulnerabilitiesAPIVersion = "security.aquasecurity.github.com/v1alpha1"
vulnerabilitiesKind = "ImageScanReport"
)

type Workload struct {
Kind string
Name string
Expand All @@ -33,7 +51,7 @@ type ContainerImageScanReport struct {
}

func (r *Repository) GetVulnerabilitiesSummary(ctx context.Context, options Workload) (vs security.VulnerabilitiesSummary, err error) {
containerReports, err := r.GetImageScanReports(ctx, options)
containerReports, err := r.GetVulnerabilitiesForWorkload(ctx, options)
if err != nil {
return vs, err
}
Expand All @@ -56,57 +74,86 @@ func (r *Repository) GetVulnerabilitiesSummary(ctx context.Context, options Work
return
}

func (r *Repository) GetImageScanReports(ctx context.Context, options Workload) ([]ContainerImageScanReport, error) {
func (r *Repository) GetVulnerabilitiesForNamespace(ctx context.Context, namespace string) (report ContainerImageScanReport, err error) {
unstructuredList, err := r.client.List(ctx, store.Key{
APIVersion: "security.aquasecurity.github.com/v1",
Kind: "ImageScanReport",
//Selector: &labels.Set{
// "risky.workload.kind": options.Kind,
// "risky.workload.name": options.Name,
// "risky.container.name": options.Container,
//},
APIVersion: vulnerabilitiesAPIVersion,
Kind: vulnerabilitiesKind,
Namespace: namespace,
})
if err != nil {
return nil, err
return
}
b, err := unstructuredList.MarshalJSON()
if err != nil {
return nil, err
return
}
var reportList security.ImageScanReportList
err = json.Unmarshal(b, &reportList)
if err != nil {
return nil, err
return
}
var reports []ContainerImageScanReport

var vulnerabilities []security.Vulnerability

for _, i := range reportList.Items {
containerName, containerNameSpecified := i.Labels["risky.container.name"]
if i.Labels["risky.workload.kind"] == options.Kind &&
i.Labels["risky.workload.name"] == options.Name &&
containerNameSpecified {
reports = append(reports, ContainerImageScanReport{
Name: containerName,
Report: i,
})
if _, containerNameSpecified := i.Labels[labelContainerName]; !containerNameSpecified {
continue
}
vulnerabilities = append(vulnerabilities, i.Spec.Vulnerabilities...)
}

sort.SliceStable(reports, func(i, j int) bool {
return strings.Compare(reports[i].Name, reports[j].Name) < 0
sort.SliceStable(vulnerabilities, func(i, j int) bool {
return strings.Compare(vulnerabilities[i].VulnerabilityID, vulnerabilities[j].VulnerabilityID) < 0
})

return reports, nil
report = ContainerImageScanReport{
Name: fmt.Sprintf("Namespace %s", namespace),
Report: security.ImageScanReport{
Spec: security.ImageScanReportSpec{
Vulnerabilities: vulnerabilities,
},
},
}

return
}

func UnstructuredToPod(u *unstructured.Unstructured) (core.Pod, error) {
b, err := u.MarshalJSON()
func (r *Repository) GetVulnerabilitiesForWorkload(ctx context.Context, options Workload) (reports []ContainerImageScanReport, err error) {
unstructuredList, err := r.client.List(ctx, store.Key{
APIVersion: vulnerabilitiesAPIVersion,
Kind: vulnerabilitiesKind,
// TODO Report bug to Octant? Apparently the label selector doesn't work and I have to do filtering manually :(
//Selector: &labels.Set{
// labelWorkloadKind: options.Kind,
// labelWorkloadName: options.Name,
//},
})
if err != nil {
return core.Pod{}, err
err = xerrors.Errorf("listing vulnerabilities: %w", err)
return
}
var pod core.Pod
err = json.Unmarshal(b, &pod)
b, err := unstructuredList.MarshalJSON()
if err != nil {
err = xerrors.Errorf("marshalling unstructured list to JSON: %w", err)
return
}
var reportList security.ImageScanReportList
err = json.Unmarshal(b, &reportList)
if err != nil {
return core.Pod{}, err
err = xerrors.Errorf("unmarshalling JSON to ImageScanReport: %w", err)
return
}
for _, item := range reportList.Items {
containerName, containerNameSpecified := item.Labels[labelContainerName]
if item.Labels[labelWorkloadKind] == options.Kind &&
item.Labels[labelWorkloadName] == options.Name &&
containerNameSpecified {
reports = append(reports, ContainerImageScanReport{
Name: fmt.Sprintf("Container %s", containerName),
Report: item,
})
}
}
return pod, nil

return
}
4 changes: 2 additions & 2 deletions pkg/view/image_scan_report.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import (
"github.com/vmware-tanzu/octant/pkg/view/component"
)

func NewImageScanReport(containerName string, report security.ImageScanReport) component.Component {
func NewImageScanReport(reportName string, report security.ImageScanReport) component.Component {
table := component.NewTableWithRows(
fmt.Sprintf("Image Scan Report %s", containerName), "There are no vulnerabilities!",
fmt.Sprintf(reportName), "There are no vulnerabilities!",
component.NewTableCols("ID", "Severity", "Title", "Resource", "Installed Version", "Fixed Version"),
[]component.TableRow{})

Expand Down

0 comments on commit 87787cc

Please sign in to comment.