-
Notifications
You must be signed in to change notification settings - Fork 12
Update component usage and add action pattern #85
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
package actions | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"github.com/aquasecurity/starboard/pkg/generated/clientset/versioned" | ||
"github.com/aquasecurity/starboard/pkg/kube" | ||
"github.com/aquasecurity/starboard/pkg/kubehunter" | ||
"github.com/aquasecurity/starboard/pkg/starboard" | ||
"github.com/vmware-tanzu/octant/pkg/action" | ||
"github.com/vmware-tanzu/octant/pkg/plugin/service" | ||
"k8s.io/cli-runtime/pkg/genericclioptions" | ||
"k8s.io/client-go/kubernetes" | ||
"os" | ||
"time" | ||
) | ||
|
||
const ( | ||
StarboardKubeHunterScan = "starboard.octant.dev/scanKubeHunter" | ||
|
||
kubeHunterReportName = "cluster" | ||
) | ||
|
||
func ActionHandler(request *service.ActionRequest) error { | ||
actionName, err := request.Payload.String("action") | ||
if err != nil { | ||
return err | ||
} | ||
|
||
switch actionName { | ||
case StarboardKubeHunterScan: | ||
alert := action.CreateAlert(action.AlertTypeInfo, "Creating kube-hunter report...", time.Second * 15) | ||
request.DashboardClient.SendAlert(request.Context(), request.ClientState.ClientID(), alert) | ||
return startKubeHunterScan(request.Context()) | ||
default: | ||
// no-op | ||
} | ||
return nil | ||
} | ||
|
||
func startKubeHunterScan(ctx context.Context) error { | ||
configFlags := genericclioptions.NewConfigFlags(true) | ||
// TODO: Since DashboardClient does not provide a RESTConfig, don't scan if kubeconfigs are different | ||
if *configFlags.KubeConfig != os.Getenv("KUBECONFIG") { | ||
return fmt.Errorf("kubeconfig mismatch") | ||
} | ||
|
||
restconfig, err := configFlags.ToRESTConfig() | ||
if err != nil { | ||
return err | ||
} | ||
kubeClientset, err := kubernetes.NewForConfig(restconfig) | ||
if err != nil { | ||
return err | ||
} | ||
opts := kube.ScannerOpts{ScanJobTimeout: time.Minute} | ||
config, err := starboard.NewConfigManager(kubeClientset, starboard.NamespaceName).Read(ctx) | ||
if err != nil { | ||
return err | ||
} | ||
scanner := kubehunter.NewScanner(starboard.NewScheme(), kubeClientset, config, opts) | ||
report, err := scanner.Scan(ctx) | ||
if err != nil { | ||
return err | ||
} | ||
starboardClientset, err := versioned.NewForConfig(restconfig) | ||
if err != nil { | ||
return err | ||
} | ||
return kubehunter.NewWriter(starboardClientset).Write(ctx, report, kubeHunterReportName) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,6 @@ package controller | |
import ( | ||
"errors" | ||
"fmt" | ||
|
||
"github.com/aquasecurity/starboard-octant-plugin/pkg/plugin/model" | ||
"github.com/aquasecurity/starboard-octant-plugin/pkg/plugin/view/configaudit" | ||
"github.com/aquasecurity/starboard-octant-plugin/pkg/plugin/view/kubebench" | ||
|
@@ -15,6 +14,7 @@ import ( | |
"github.com/vmware-tanzu/octant/pkg/view/component" | ||
"k8s.io/apimachinery/pkg/api/meta" | ||
"k8s.io/apimachinery/pkg/runtime" | ||
"strconv" | ||
) | ||
|
||
// ResourceTabPrinter is called when Octant wants to add new tab for the underlying resource. | ||
|
@@ -130,11 +130,98 @@ func ResourcePrinter(request *service.PrintRequest) (plugin.PrintResponse, error | |
return plugin.PrintResponse{ | ||
Status: vulnerabilities.NewSummarySections(summary), | ||
Config: configaudit.NewSummarySections(configAuditSummary), | ||
Items: []component.FlexLayoutItem{ | ||
{ | ||
Width: component.WidthFull, | ||
View: configaudit.NewReport(workload, configAuditReportsDefined, configAuditReport), | ||
}, nil | ||
} | ||
|
||
func ResourceReportTabPrinter(request *service.PrintRequest) (plugin.TabResponse, error) { | ||
if request.Object == nil { | ||
return plugin.TabResponse{}, errors.New("object is nil") | ||
} | ||
|
||
workload, err := getWorkloadFromObject(request.Object) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We generate ConfigAuditReports for a subset of K8s resources (GVKs). For example, we'll never generate it for Nodes because for Nodes we generate CISKubeBenchReports. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Checking if the object type is a node and returning an empty tab response is one way, but I don't like having that error message. Going to patch upstream to skip empty tab responses without errors. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. An empty tab response sounds good. We can improve error handling by upgrading Octant whenever a new version is available. |
||
if err != nil { | ||
return plugin.TabResponse{}, err | ||
} | ||
|
||
if workload.Kind == kube.KindNode { | ||
return plugin.TabResponse{}, nil | ||
} | ||
|
||
repository := model.NewRepository(request.DashboardClient) | ||
|
||
_, err = repository.GetCustomResourceDefinitionByName(request.Context(), v1alpha1.ConfigAuditReportCRName) | ||
configAuditReportsDefined := err == nil | ||
|
||
var configAuditReport *v1alpha1.ConfigAuditReport | ||
if configAuditReportsDefined { | ||
configAuditReport, err = repository.GetConfigAuditReportByOwner(request.Context(), workload) | ||
if err != nil { | ||
return plugin.TabResponse{}, err | ||
} | ||
} | ||
|
||
return plugin.TabResponse{ | ||
Tab: component.NewTabWithContents(*configaudit.NewReport(workload, configAuditReportsDefined, configAuditReport)), | ||
}, nil | ||
} | ||
|
||
// ResourceObjectStatus is called when Octant wants to determine the status (icon color) of an object | ||
func ResourceObjectStatus(request *service.PrintRequest) (plugin.ObjectStatusResponse, error) { | ||
if request.Object == nil { | ||
return plugin.ObjectStatusResponse{}, errors.New("object is nil") | ||
} | ||
|
||
workload, err := getWorkloadFromObject(request.Object) | ||
if err != nil { | ||
return plugin.ObjectStatusResponse{}, err | ||
} | ||
|
||
repository := model.NewRepository(request.DashboardClient) | ||
|
||
_, err = repository.GetCustomResourceDefinitionByName(request.Context(), v1alpha1.VulnerabilityReportsCRName) | ||
vulnerabilityReportsDefined := err == nil | ||
|
||
var summary *v1alpha1.VulnerabilitySummary | ||
if vulnerabilityReportsDefined { | ||
summary, err = repository.GetVulnerabilitiesSummary(request.Context(), workload) | ||
if err != nil { | ||
return plugin.ObjectStatusResponse{}, err | ||
} | ||
} | ||
// summary could be nil due to delays in fetching the CRDs | ||
if summary == nil { | ||
return plugin.ObjectStatusResponse{}, nil | ||
} | ||
|
||
status := vulnerabilities.NewSummaryStatus(summary) | ||
|
||
return plugin.ObjectStatusResponse{ | ||
ObjectStatus: component.PodSummary{ | ||
Details: []component.Component{ | ||
component.NewLabels(map[string]string{ | ||
"Critical Vulnerabilities": strconv.Itoa(summary.CriticalCount), | ||
}), | ||
component.NewLabels(map[string]string{ | ||
"High Vulnerabilities": strconv.Itoa(summary.HighCount), | ||
}), | ||
component.NewLabels(map[string]string{ | ||
"Medium Vulnerabilities": strconv.Itoa(summary.MediumCount), | ||
}), | ||
component.NewLabels(map[string]string{ | ||
"Low Vulnerabilities": strconv.Itoa(summary.LowCount), | ||
}), | ||
component.NewLabels(map[string]string{ | ||
"Unknown Vulnerabilities": strconv.Itoa(summary.UnknownCount), | ||
}), | ||
}, | ||
Properties: []component.Property{ | ||
{Label: "Critical Vulnerabilities", Value: component.NewText(strconv.Itoa(summary.CriticalCount))}, | ||
{Label: "High Vulnerabilities", Value: component.NewText(strconv.Itoa(summary.HighCount))}, | ||
{Label: "Medium Vulnerabilities", Value: component.NewText(strconv.Itoa(summary.MediumCount))}, | ||
{Label: "Low Vulnerabilities", Value: component.NewText(strconv.Itoa(summary.LowCount))}, | ||
{Label: "Unknown Vulnerabilities", Value: component.NewText(strconv.Itoa(summary.UnknownCount))}, | ||
}, | ||
Status: status, | ||
}, | ||
}, nil | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this in general may be a different client config than the one used by octant process. Is that correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is a good point and leaves more to be desired in the plugin interface.
A workaround is to see if
$KUBECONFIG
is set and see if it matches the one from config flags since we can assume Octant looks at the same spot by default viaclientcmd.NewDefaultClientConfigLoadingRules().GetDefaultFilename()
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for clarification. I think it's a good start and it should work in most cases. We can also leave a TODO comment explaining when this approach may not work.