Skip to content

Commit

Permalink
Merge pull request #24 from hebestreit/feature/workload
Browse files Browse the repository at this point in the history
added metrics for vulnerabilities on a workload level
  • Loading branch information
matthyx authored Dec 18, 2024
2 parents ae678f2 + 657e3a7 commit a63a525
Show file tree
Hide file tree
Showing 5 changed files with 443 additions and 3 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ go run main.go
```
The exporter will start collecting security metrics from the Kubernetes cluster and exposing them for Prometheus to scrape.

If you also want to collect metrics on a workload level you need to set an environment variable `ENABLE_WORKLOAD_METRICS=true`. Keep in mind that this will increase the number of metrics exposed by the exporter.

6. Accessing Metrics:

To access the exported metrics directly from the exporter, open your web browser and go to: `http://localhost:8080/metrics`
Expand Down
52 changes: 49 additions & 3 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ package api

import (
"context"

"github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/k8s-interface/k8sinterface"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1"
spdxclient "github.com/kubescape/storage/pkg/generated/clientset/versioned"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/tools/pager"
)

type StorageClientImpl struct {
Expand All @@ -33,6 +34,28 @@ func NewStorageClient() *StorageClientImpl {
}
}

func (sc *StorageClientImpl) WatchVulnerabilityManifestSummaries() (watch.Interface, error) {
return sc.clientset.SpdxV1beta1().VulnerabilityManifestSummaries("").Watch(context.Background(), metav1.ListOptions{})
}

func (sc *StorageClientImpl) GetVulnerabilityManifestSummaries() (*v1beta1.VulnerabilityManifestSummaryList, error) {
var list v1beta1.VulnerabilityManifestSummaryList
err := pager.New(func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) {
return sc.clientset.SpdxV1beta1().VulnerabilityManifestSummaries("").List(ctx, opts)
}).EachListItem(context.TODO(), metav1.ListOptions{}, func(obj runtime.Object) error {
// enrich the summary list with the full object as the list only contains the metadata
summary := obj.(*v1beta1.VulnerabilityManifestSummary)
item, err := sc.clientset.SpdxV1beta1().VulnerabilityManifestSummaries(summary.Namespace).Get(context.TODO(), summary.Name, metav1.GetOptions{})
if err != nil {
return err
}
list.Items = append(list.Items, *item)
return nil
})

return &list, err
}

func (sc *StorageClientImpl) GetVulnerabilitySummaries() (*v1beta1.VulnerabilitySummaryList, error) {
vulnsummary, err := sc.clientset.SpdxV1beta1().VulnerabilitySummaries("").List(context.TODO(), metav1.ListOptions{})
if err != nil {
Expand All @@ -43,6 +66,29 @@ func (sc *StorageClientImpl) GetVulnerabilitySummaries() (*v1beta1.Vulnerability

}

func (sc *StorageClientImpl) WatchWorkloadConfigurationScanSummaries() (watch.Interface, error) {
return sc.clientset.SpdxV1beta1().WorkloadConfigurationScanSummaries("").Watch(context.Background(), metav1.ListOptions{})
}

func (sc *StorageClientImpl) GetWorkloadConfigurationScanSummaries() (*v1beta1.WorkloadConfigurationScanSummaryList, error) {
var list v1beta1.WorkloadConfigurationScanSummaryList
err := pager.New(func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) {
return sc.clientset.SpdxV1beta1().WorkloadConfigurationScanSummaries("").List(ctx, opts)
}).EachListItem(context.TODO(), metav1.ListOptions{}, func(obj runtime.Object) error {
// enrich the summary list with the full object as the list only contains the metadata
summary := obj.(*v1beta1.WorkloadConfigurationScanSummary)
item, err := sc.clientset.SpdxV1beta1().WorkloadConfigurationScanSummaries(summary.Namespace).Get(context.TODO(), summary.Name, metav1.GetOptions{})
if err != nil {
return err
}
list.Items = append(list.Items, *item)

return nil
})

return &list, err
}

func (sc *StorageClientImpl) GetConfigScanSummaries() (*v1beta1.ConfigurationScanSummaryList, error) {
configscan, err := sc.clientset.SpdxV1beta1().ConfigurationScanSummaries("").List(context.TODO(), metav1.ListOptions{})
if err != nil {
Expand Down
59 changes: 59 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package main

import (
"github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1"
"k8s.io/apimachinery/pkg/watch"
"net/http"
"os"
"time"

"github.com/kubescape/go-logger"
Expand All @@ -22,6 +25,13 @@ func main() {
logger.L().Fatal(http.ListenAndServe(":8080", nil).Error())
}()

if os.Getenv("ENABLE_WORKLOAD_METRICS") == "true" {
handleWorkloadConfigScanSummaries(storageClient)
handleWorkloadVulnScanSummaries(storageClient)
go watchWorkloadConfigScanSummaries(storageClient)
go watchWorkloadVulnScanSummaries(storageClient)
}

// monitor the severities in objects
for {
handleConfigScanSummaries(storageClient)
Expand All @@ -33,6 +43,47 @@ func main() {

}

func watchWorkloadVulnScanSummaries(storageClient *api.StorageClientImpl) {
watcher, _ := storageClient.WatchVulnerabilityManifestSummaries()
for event := range watcher.ResultChan() {
item := event.Object.(*v1beta1.VulnerabilityManifestSummary)
if event.Type == watch.Added || event.Type == watch.Modified {
metrics.ProcessVulnWorkloadMetrics(&v1beta1.VulnerabilityManifestSummaryList{
Items: []v1beta1.VulnerabilityManifestSummary{*item},
})
}

if event.Type == watch.Deleted {
metrics.DeleteVulnWorkloadMetric(item)
}
}
}

func watchWorkloadConfigScanSummaries(storageClient *api.StorageClientImpl) {
watcher, _ := storageClient.WatchWorkloadConfigurationScanSummaries()
for event := range watcher.ResultChan() {
item := event.Object.(*v1beta1.WorkloadConfigurationScanSummary)
if event.Type == watch.Added || event.Type == watch.Modified {
metrics.ProcessConfigscanWorkloadMetrics(&v1beta1.WorkloadConfigurationScanSummaryList{
Items: []v1beta1.WorkloadConfigurationScanSummary{*item},
})
}

if event.Type == watch.Deleted {
metrics.DeleteConfigscanWorkloadMetric(item)
}
}
}

func handleWorkloadConfigScanSummaries(storageClient *api.StorageClientImpl) {
workloadConfigurationScanSummaries, err := storageClient.GetWorkloadConfigurationScanSummaries()
if err != nil {
logger.L().Warning("failed getting workload configuration scan summaries", helpers.Error(err))
return
}
metrics.ProcessConfigscanWorkloadMetrics(workloadConfigurationScanSummaries)
}

func handleConfigScanSummaries(storageClient *api.StorageClientImpl) {
configScanSummaries, err := storageClient.GetConfigScanSummaries()
if err != nil {
Expand All @@ -42,7 +93,15 @@ func handleConfigScanSummaries(storageClient *api.StorageClientImpl) {

metrics.ProcessConfigscanClusterMetrics(configScanSummaries)
metrics.ProcessConfigscanNamespaceMetrics(configScanSummaries)
}

func handleWorkloadVulnScanSummaries(storageClient *api.StorageClientImpl) {
vulnerabilityManifestSummaries, err := storageClient.GetVulnerabilityManifestSummaries()
if err != nil {
logger.L().Warning("failed getting vulnerability manifest summaries", helpers.Error(err))
return
}
metrics.ProcessVulnWorkloadMetrics(vulnerabilityManifestSummaries)
}

func handleVulnScanSummaries(storageClient *api.StorageClientImpl) {
Expand Down
158 changes: 158 additions & 0 deletions metrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,36 @@ package metrics
import (
"github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1"
"github.com/prometheus/client_golang/prometheus"
"os"
"strings"
)

var (
workloadCritical = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: "kubescape_controls_total_workload_critical",
Help: "Total number of critical vulnerabilities in the workload",
}, []string{"namespace", "workload", "workload_kind"})

workloadHigh = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: "kubescape_controls_total_workload_high",
Help: "Total number of high vulnerabilities in the workload",
}, []string{"namespace", "workload", "workload_kind"})

workloadMedium = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: "kubescape_controls_total_workload_medium",
Help: "Total number of medium vulnerabilities in the workload",
}, []string{"namespace", "workload", "workload_kind"})

workloadLow = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: "kubescape_controls_total_workload_low",
Help: "Total number of low vulnerabilities in the workload",
}, []string{"namespace", "workload", "workload_kind"})

workloadUnknown = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: "kubescape_controls_total_workload_unknown",
Help: "Total number of unknown vulnerabilities in the workload",
}, []string{"namespace", "workload", "workload_kind"})

namespaceCritical = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: "kubescape_controls_total_namespace_critical",
Help: "Total number of critical vulnerabilities in the namespace",
Expand Down Expand Up @@ -55,6 +82,31 @@ var (
Help: "Total number of unknown vulnerabilities in the cluster",
})

workloadVulnCritical = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: "kubescape_vulnerabilities_total_workload_critical",
Help: "Total number of critical vulnerabilities in the workload",
}, []string{"namespace", "workload", "workload_kind", "workload_container_name"})

workloadVulnHigh = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: "kubescape_vulnerabilities_total_workload_high",
Help: "Total number of high vulnerabilities in the workload",
}, []string{"namespace", "workload", "workload_kind", "workload_container_name"})

workloadVulnMedium = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: "kubescape_vulnerabilities_total_workload_medium",
Help: "Total number of medium vulnerabilities in the workload",
}, []string{"namespace", "workload", "workload_kind", "workload_container_name"})

workloadVulnLow = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: "kubescape_vulnerabilities_total_workload_low",
Help: "Total number of low vulnerabilities in the workload",
}, []string{"namespace", "workload", "workload_kind", "workload_container_name"})

workloadVulnUnknown = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: "kubescape_vulnerabilities_total_workload_unknown",
Help: "Total number of unknown vulnerabilities in the workload",
}, []string{"namespace", "workload", "workload_kind", "workload_container_name"})

namespaceVulnCritical = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: "kubescape_vulnerabilities_total_namespace_critical",
Help: "Total number of critical vulnerabilities in the namespace",
Expand Down Expand Up @@ -104,6 +156,31 @@ var (
Help: "Total number of unknown vulnerabilities in the cluster",
})

workloadVulnCriticalRelevant = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: "kubescape_vulnerabilities_relevant_workload_critical",
Help: "Number of relevant critical vulnerabilities in the workload",
}, []string{"namespace", "workload", "workload_kind", "workload_container_name"})

workloadVulnHighRelevant = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: "kubescape_vulnerabilities_relevant_workload_high",
Help: "Number of relevant high vulnerabilities in the workload",
}, []string{"namespace", "workload", "workload_kind", "workload_container_name"})

workloadVulnMediumRelevant = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: "kubescape_vulnerabilities_relevant_workload_medium",
Help: "Number of relevant medium vulnerabilities in the workload",
}, []string{"namespace", "workload", "workload_kind", "workload_container_name"})

workloadVulnLowRelevant = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: "kubescape_vulnerabilities_relevant_workload_low",
Help: "Number of relevant low vulnerabilities in the workload",
}, []string{"namespace", "workload", "workload_kind", "workload_container_name"})

workloadVulnUnknownRelevant = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: "kubescape_vulnerabilities_relevant_workload_unknown",
Help: "Number of relevant unknown vulnerabilities in the workload",
}, []string{"namespace", "workload", "workload_kind", "workload_container_name"})

namespaceVulnCriticalRelevant = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: "kubescape_vulnerabilities_relevant_namespace_critical",
Help: "Number of relevant critical vulnerabilities in the namespace",
Expand Down Expand Up @@ -156,6 +233,23 @@ var (
)

func init() {
if os.Getenv("ENABLE_WORKLOAD_METRICS") == "true" {
prometheus.MustRegister(workloadCritical)
prometheus.MustRegister(workloadHigh)
prometheus.MustRegister(workloadMedium)
prometheus.MustRegister(workloadLow)
prometheus.MustRegister(workloadUnknown)
prometheus.MustRegister(workloadVulnCritical)
prometheus.MustRegister(workloadVulnHigh)
prometheus.MustRegister(workloadVulnMedium)
prometheus.MustRegister(workloadVulnLow)
prometheus.MustRegister(workloadVulnUnknown)
prometheus.MustRegister(workloadVulnCriticalRelevant)
prometheus.MustRegister(workloadVulnHighRelevant)
prometheus.MustRegister(workloadVulnMediumRelevant)
prometheus.MustRegister(workloadVulnLowRelevant)
prometheus.MustRegister(workloadVulnUnknownRelevant)
}
prometheus.MustRegister(namespaceCritical)
prometheus.MustRegister(namespaceHigh)
prometheus.MustRegister(namespaceMedium)
Expand Down Expand Up @@ -188,6 +282,32 @@ func init() {
prometheus.MustRegister(clusterVulnUnknownRelevant)
}

func ProcessConfigscanWorkloadMetrics(summary *v1beta1.WorkloadConfigurationScanSummaryList) {
for _, item := range summary.Items {
namespace := item.ObjectMeta.Labels["kubescape.io/workload-namespace"]
workload := item.ObjectMeta.Labels["kubescape.io/workload-name"]
kind := strings.ToLower(item.ObjectMeta.Labels["kubescape.io/workload-kind"])

workloadCritical.WithLabelValues(namespace, workload, kind).Set(float64(item.Spec.Severities.Critical))
workloadHigh.WithLabelValues(namespace, workload, kind).Set(float64(item.Spec.Severities.High))
workloadLow.WithLabelValues(namespace, workload, kind).Set(float64(item.Spec.Severities.Low))
workloadMedium.WithLabelValues(namespace, workload, kind).Set(float64(item.Spec.Severities.Medium))
workloadUnknown.WithLabelValues(namespace, workload, kind).Set(float64(item.Spec.Severities.Unknown))
}
}

func DeleteConfigscanWorkloadMetric(item *v1beta1.WorkloadConfigurationScanSummary) {
namespace := item.ObjectMeta.Labels["kubescape.io/workload-namespace"]
workload := item.ObjectMeta.Labels["kubescape.io/workload-name"]
kind := strings.ToLower(item.ObjectMeta.Labels["kubescape.io/workload-kind"])

workloadCritical.DeleteLabelValues(namespace, workload, kind)
workloadHigh.DeleteLabelValues(namespace, workload, kind)
workloadMedium.DeleteLabelValues(namespace, workload, kind)
workloadLow.DeleteLabelValues(namespace, workload, kind)
workloadUnknown.DeleteLabelValues(namespace, workload, kind)
}

func ProcessConfigscanNamespaceMetrics(summary *v1beta1.ConfigurationScanSummaryList) {
for _, item := range summary.Items {
namespace := item.ObjectMeta.Name
Expand Down Expand Up @@ -218,6 +338,44 @@ func ProcessConfigscanClusterMetrics(summary *v1beta1.ConfigurationScanSummaryLi
return totalCritical, totalHigh, totalMedium, totalLow, totalUnknown
}

func ProcessVulnWorkloadMetrics(summary *v1beta1.VulnerabilityManifestSummaryList) {
for _, item := range summary.Items {
namespace := item.ObjectMeta.Labels["kubescape.io/workload-namespace"]
workload := item.ObjectMeta.Labels["kubescape.io/workload-name"]
kind := strings.ToLower(item.ObjectMeta.Labels["kubescape.io/workload-kind"])
containerName := strings.ToLower(item.ObjectMeta.Labels["kubescape.io/workload-container-name"])

workloadVulnCritical.WithLabelValues(namespace, workload, kind, containerName).Set(float64(item.Spec.Severities.Critical.All))
workloadVulnHigh.WithLabelValues(namespace, workload, kind, containerName).Set(float64(item.Spec.Severities.High.All))
workloadVulnMedium.WithLabelValues(namespace, workload, kind, containerName).Set(float64(item.Spec.Severities.Medium.All))
workloadVulnLow.WithLabelValues(namespace, workload, kind, containerName).Set(float64(item.Spec.Severities.Low.All))
workloadVulnUnknown.WithLabelValues(namespace, workload, kind, containerName).Set(float64(item.Spec.Severities.Unknown.All))
workloadVulnCriticalRelevant.WithLabelValues(namespace, workload, kind, containerName).Set(float64(item.Spec.Severities.Critical.Relevant))
workloadVulnHighRelevant.WithLabelValues(namespace, workload, kind, containerName).Set(float64(item.Spec.Severities.High.Relevant))
workloadVulnMediumRelevant.WithLabelValues(namespace, workload, kind, containerName).Set(float64(item.Spec.Severities.Medium.Relevant))
workloadVulnLowRelevant.WithLabelValues(namespace, workload, kind, containerName).Set(float64(item.Spec.Severities.Low.Relevant))
workloadVulnUnknownRelevant.WithLabelValues(namespace, workload, kind, containerName).Set(float64(item.Spec.Severities.Unknown.Relevant))
}
}

func DeleteVulnWorkloadMetric(item *v1beta1.VulnerabilityManifestSummary) {
namespace := item.ObjectMeta.Labels["kubescape.io/workload-namespace"]
workload := item.ObjectMeta.Labels["kubescape.io/workload-name"]
kind := strings.ToLower(item.ObjectMeta.Labels["kubescape.io/workload-kind"])
containerName := strings.ToLower(item.ObjectMeta.Labels["kubescape.io/workload-container-name"])

workloadVulnCritical.DeleteLabelValues(namespace, workload, kind, containerName)
workloadVulnHigh.DeleteLabelValues(namespace, workload, kind, containerName)
workloadVulnMedium.DeleteLabelValues(namespace, workload, kind, containerName)
workloadVulnLow.DeleteLabelValues(namespace, workload, kind, containerName)
workloadVulnUnknown.DeleteLabelValues(namespace, workload, kind, containerName)
workloadVulnCriticalRelevant.DeleteLabelValues(namespace, workload, kind, containerName)
workloadVulnHighRelevant.DeleteLabelValues(namespace, workload, kind, containerName)
workloadVulnMediumRelevant.DeleteLabelValues(namespace, workload, kind, containerName)
workloadVulnLowRelevant.DeleteLabelValues(namespace, workload, kind, containerName)
workloadVulnUnknownRelevant.DeleteLabelValues(namespace, workload, kind, containerName)
}

func ProcessVulnNamespaceMetrics(summary *v1beta1.VulnerabilitySummaryList) {
for _, item := range summary.Items {
namespace := item.ObjectMeta.Name
Expand Down
Loading

0 comments on commit a63a525

Please sign in to comment.