Skip to content

Commit a63a525

Browse files
authored
Merge pull request #24 from hebestreit/feature/workload
added metrics for vulnerabilities on a workload level
2 parents ae678f2 + 657e3a7 commit a63a525

File tree

5 files changed

+443
-3
lines changed

5 files changed

+443
-3
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ go run main.go
4545
```
4646
The exporter will start collecting security metrics from the Kubernetes cluster and exposing them for Prometheus to scrape.
4747

48+
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.
49+
4850
6. Accessing Metrics:
4951

5052
To access the exported metrics directly from the exporter, open your web browser and go to: `http://localhost:8080/metrics`

api/api.go

+49-3
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ package api
22

33
import (
44
"context"
5-
65
"github.com/kubescape/go-logger"
76
"github.com/kubescape/go-logger/helpers"
87
"github.com/kubescape/k8s-interface/k8sinterface"
9-
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10-
118
"github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1"
129
spdxclient "github.com/kubescape/storage/pkg/generated/clientset/versioned"
10+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
"k8s.io/apimachinery/pkg/runtime"
12+
"k8s.io/apimachinery/pkg/watch"
13+
"k8s.io/client-go/tools/pager"
1314
)
1415

1516
type StorageClientImpl struct {
@@ -33,6 +34,28 @@ func NewStorageClient() *StorageClientImpl {
3334
}
3435
}
3536

37+
func (sc *StorageClientImpl) WatchVulnerabilityManifestSummaries() (watch.Interface, error) {
38+
return sc.clientset.SpdxV1beta1().VulnerabilityManifestSummaries("").Watch(context.Background(), metav1.ListOptions{})
39+
}
40+
41+
func (sc *StorageClientImpl) GetVulnerabilityManifestSummaries() (*v1beta1.VulnerabilityManifestSummaryList, error) {
42+
var list v1beta1.VulnerabilityManifestSummaryList
43+
err := pager.New(func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) {
44+
return sc.clientset.SpdxV1beta1().VulnerabilityManifestSummaries("").List(ctx, opts)
45+
}).EachListItem(context.TODO(), metav1.ListOptions{}, func(obj runtime.Object) error {
46+
// enrich the summary list with the full object as the list only contains the metadata
47+
summary := obj.(*v1beta1.VulnerabilityManifestSummary)
48+
item, err := sc.clientset.SpdxV1beta1().VulnerabilityManifestSummaries(summary.Namespace).Get(context.TODO(), summary.Name, metav1.GetOptions{})
49+
if err != nil {
50+
return err
51+
}
52+
list.Items = append(list.Items, *item)
53+
return nil
54+
})
55+
56+
return &list, err
57+
}
58+
3659
func (sc *StorageClientImpl) GetVulnerabilitySummaries() (*v1beta1.VulnerabilitySummaryList, error) {
3760
vulnsummary, err := sc.clientset.SpdxV1beta1().VulnerabilitySummaries("").List(context.TODO(), metav1.ListOptions{})
3861
if err != nil {
@@ -43,6 +66,29 @@ func (sc *StorageClientImpl) GetVulnerabilitySummaries() (*v1beta1.Vulnerability
4366

4467
}
4568

69+
func (sc *StorageClientImpl) WatchWorkloadConfigurationScanSummaries() (watch.Interface, error) {
70+
return sc.clientset.SpdxV1beta1().WorkloadConfigurationScanSummaries("").Watch(context.Background(), metav1.ListOptions{})
71+
}
72+
73+
func (sc *StorageClientImpl) GetWorkloadConfigurationScanSummaries() (*v1beta1.WorkloadConfigurationScanSummaryList, error) {
74+
var list v1beta1.WorkloadConfigurationScanSummaryList
75+
err := pager.New(func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) {
76+
return sc.clientset.SpdxV1beta1().WorkloadConfigurationScanSummaries("").List(ctx, opts)
77+
}).EachListItem(context.TODO(), metav1.ListOptions{}, func(obj runtime.Object) error {
78+
// enrich the summary list with the full object as the list only contains the metadata
79+
summary := obj.(*v1beta1.WorkloadConfigurationScanSummary)
80+
item, err := sc.clientset.SpdxV1beta1().WorkloadConfigurationScanSummaries(summary.Namespace).Get(context.TODO(), summary.Name, metav1.GetOptions{})
81+
if err != nil {
82+
return err
83+
}
84+
list.Items = append(list.Items, *item)
85+
86+
return nil
87+
})
88+
89+
return &list, err
90+
}
91+
4692
func (sc *StorageClientImpl) GetConfigScanSummaries() (*v1beta1.ConfigurationScanSummaryList, error) {
4793
configscan, err := sc.clientset.SpdxV1beta1().ConfigurationScanSummaries("").List(context.TODO(), metav1.ListOptions{})
4894
if err != nil {

main.go

+59
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package main
22

33
import (
4+
"github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1"
5+
"k8s.io/apimachinery/pkg/watch"
46
"net/http"
7+
"os"
58
"time"
69

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

28+
if os.Getenv("ENABLE_WORKLOAD_METRICS") == "true" {
29+
handleWorkloadConfigScanSummaries(storageClient)
30+
handleWorkloadVulnScanSummaries(storageClient)
31+
go watchWorkloadConfigScanSummaries(storageClient)
32+
go watchWorkloadVulnScanSummaries(storageClient)
33+
}
34+
2535
// monitor the severities in objects
2636
for {
2737
handleConfigScanSummaries(storageClient)
@@ -33,6 +43,47 @@ func main() {
3343

3444
}
3545

46+
func watchWorkloadVulnScanSummaries(storageClient *api.StorageClientImpl) {
47+
watcher, _ := storageClient.WatchVulnerabilityManifestSummaries()
48+
for event := range watcher.ResultChan() {
49+
item := event.Object.(*v1beta1.VulnerabilityManifestSummary)
50+
if event.Type == watch.Added || event.Type == watch.Modified {
51+
metrics.ProcessVulnWorkloadMetrics(&v1beta1.VulnerabilityManifestSummaryList{
52+
Items: []v1beta1.VulnerabilityManifestSummary{*item},
53+
})
54+
}
55+
56+
if event.Type == watch.Deleted {
57+
metrics.DeleteVulnWorkloadMetric(item)
58+
}
59+
}
60+
}
61+
62+
func watchWorkloadConfigScanSummaries(storageClient *api.StorageClientImpl) {
63+
watcher, _ := storageClient.WatchWorkloadConfigurationScanSummaries()
64+
for event := range watcher.ResultChan() {
65+
item := event.Object.(*v1beta1.WorkloadConfigurationScanSummary)
66+
if event.Type == watch.Added || event.Type == watch.Modified {
67+
metrics.ProcessConfigscanWorkloadMetrics(&v1beta1.WorkloadConfigurationScanSummaryList{
68+
Items: []v1beta1.WorkloadConfigurationScanSummary{*item},
69+
})
70+
}
71+
72+
if event.Type == watch.Deleted {
73+
metrics.DeleteConfigscanWorkloadMetric(item)
74+
}
75+
}
76+
}
77+
78+
func handleWorkloadConfigScanSummaries(storageClient *api.StorageClientImpl) {
79+
workloadConfigurationScanSummaries, err := storageClient.GetWorkloadConfigurationScanSummaries()
80+
if err != nil {
81+
logger.L().Warning("failed getting workload configuration scan summaries", helpers.Error(err))
82+
return
83+
}
84+
metrics.ProcessConfigscanWorkloadMetrics(workloadConfigurationScanSummaries)
85+
}
86+
3687
func handleConfigScanSummaries(storageClient *api.StorageClientImpl) {
3788
configScanSummaries, err := storageClient.GetConfigScanSummaries()
3889
if err != nil {
@@ -42,7 +93,15 @@ func handleConfigScanSummaries(storageClient *api.StorageClientImpl) {
4293

4394
metrics.ProcessConfigscanClusterMetrics(configScanSummaries)
4495
metrics.ProcessConfigscanNamespaceMetrics(configScanSummaries)
96+
}
4597

98+
func handleWorkloadVulnScanSummaries(storageClient *api.StorageClientImpl) {
99+
vulnerabilityManifestSummaries, err := storageClient.GetVulnerabilityManifestSummaries()
100+
if err != nil {
101+
logger.L().Warning("failed getting vulnerability manifest summaries", helpers.Error(err))
102+
return
103+
}
104+
metrics.ProcessVulnWorkloadMetrics(vulnerabilityManifestSummaries)
46105
}
47106

48107
func handleVulnScanSummaries(storageClient *api.StorageClientImpl) {

metrics/metrics.go

+158
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,36 @@ package metrics
33
import (
44
"github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1"
55
"github.com/prometheus/client_golang/prometheus"
6+
"os"
7+
"strings"
68
)
79

810
var (
11+
workloadCritical = prometheus.NewGaugeVec(prometheus.GaugeOpts{
12+
Name: "kubescape_controls_total_workload_critical",
13+
Help: "Total number of critical vulnerabilities in the workload",
14+
}, []string{"namespace", "workload", "workload_kind"})
15+
16+
workloadHigh = prometheus.NewGaugeVec(prometheus.GaugeOpts{
17+
Name: "kubescape_controls_total_workload_high",
18+
Help: "Total number of high vulnerabilities in the workload",
19+
}, []string{"namespace", "workload", "workload_kind"})
20+
21+
workloadMedium = prometheus.NewGaugeVec(prometheus.GaugeOpts{
22+
Name: "kubescape_controls_total_workload_medium",
23+
Help: "Total number of medium vulnerabilities in the workload",
24+
}, []string{"namespace", "workload", "workload_kind"})
25+
26+
workloadLow = prometheus.NewGaugeVec(prometheus.GaugeOpts{
27+
Name: "kubescape_controls_total_workload_low",
28+
Help: "Total number of low vulnerabilities in the workload",
29+
}, []string{"namespace", "workload", "workload_kind"})
30+
31+
workloadUnknown = prometheus.NewGaugeVec(prometheus.GaugeOpts{
32+
Name: "kubescape_controls_total_workload_unknown",
33+
Help: "Total number of unknown vulnerabilities in the workload",
34+
}, []string{"namespace", "workload", "workload_kind"})
35+
936
namespaceCritical = prometheus.NewGaugeVec(prometheus.GaugeOpts{
1037
Name: "kubescape_controls_total_namespace_critical",
1138
Help: "Total number of critical vulnerabilities in the namespace",
@@ -55,6 +82,31 @@ var (
5582
Help: "Total number of unknown vulnerabilities in the cluster",
5683
})
5784

85+
workloadVulnCritical = prometheus.NewGaugeVec(prometheus.GaugeOpts{
86+
Name: "kubescape_vulnerabilities_total_workload_critical",
87+
Help: "Total number of critical vulnerabilities in the workload",
88+
}, []string{"namespace", "workload", "workload_kind", "workload_container_name"})
89+
90+
workloadVulnHigh = prometheus.NewGaugeVec(prometheus.GaugeOpts{
91+
Name: "kubescape_vulnerabilities_total_workload_high",
92+
Help: "Total number of high vulnerabilities in the workload",
93+
}, []string{"namespace", "workload", "workload_kind", "workload_container_name"})
94+
95+
workloadVulnMedium = prometheus.NewGaugeVec(prometheus.GaugeOpts{
96+
Name: "kubescape_vulnerabilities_total_workload_medium",
97+
Help: "Total number of medium vulnerabilities in the workload",
98+
}, []string{"namespace", "workload", "workload_kind", "workload_container_name"})
99+
100+
workloadVulnLow = prometheus.NewGaugeVec(prometheus.GaugeOpts{
101+
Name: "kubescape_vulnerabilities_total_workload_low",
102+
Help: "Total number of low vulnerabilities in the workload",
103+
}, []string{"namespace", "workload", "workload_kind", "workload_container_name"})
104+
105+
workloadVulnUnknown = prometheus.NewGaugeVec(prometheus.GaugeOpts{
106+
Name: "kubescape_vulnerabilities_total_workload_unknown",
107+
Help: "Total number of unknown vulnerabilities in the workload",
108+
}, []string{"namespace", "workload", "workload_kind", "workload_container_name"})
109+
58110
namespaceVulnCritical = prometheus.NewGaugeVec(prometheus.GaugeOpts{
59111
Name: "kubescape_vulnerabilities_total_namespace_critical",
60112
Help: "Total number of critical vulnerabilities in the namespace",
@@ -104,6 +156,31 @@ var (
104156
Help: "Total number of unknown vulnerabilities in the cluster",
105157
})
106158

159+
workloadVulnCriticalRelevant = prometheus.NewGaugeVec(prometheus.GaugeOpts{
160+
Name: "kubescape_vulnerabilities_relevant_workload_critical",
161+
Help: "Number of relevant critical vulnerabilities in the workload",
162+
}, []string{"namespace", "workload", "workload_kind", "workload_container_name"})
163+
164+
workloadVulnHighRelevant = prometheus.NewGaugeVec(prometheus.GaugeOpts{
165+
Name: "kubescape_vulnerabilities_relevant_workload_high",
166+
Help: "Number of relevant high vulnerabilities in the workload",
167+
}, []string{"namespace", "workload", "workload_kind", "workload_container_name"})
168+
169+
workloadVulnMediumRelevant = prometheus.NewGaugeVec(prometheus.GaugeOpts{
170+
Name: "kubescape_vulnerabilities_relevant_workload_medium",
171+
Help: "Number of relevant medium vulnerabilities in the workload",
172+
}, []string{"namespace", "workload", "workload_kind", "workload_container_name"})
173+
174+
workloadVulnLowRelevant = prometheus.NewGaugeVec(prometheus.GaugeOpts{
175+
Name: "kubescape_vulnerabilities_relevant_workload_low",
176+
Help: "Number of relevant low vulnerabilities in the workload",
177+
}, []string{"namespace", "workload", "workload_kind", "workload_container_name"})
178+
179+
workloadVulnUnknownRelevant = prometheus.NewGaugeVec(prometheus.GaugeOpts{
180+
Name: "kubescape_vulnerabilities_relevant_workload_unknown",
181+
Help: "Number of relevant unknown vulnerabilities in the workload",
182+
}, []string{"namespace", "workload", "workload_kind", "workload_container_name"})
183+
107184
namespaceVulnCriticalRelevant = prometheus.NewGaugeVec(prometheus.GaugeOpts{
108185
Name: "kubescape_vulnerabilities_relevant_namespace_critical",
109186
Help: "Number of relevant critical vulnerabilities in the namespace",
@@ -156,6 +233,23 @@ var (
156233
)
157234

158235
func init() {
236+
if os.Getenv("ENABLE_WORKLOAD_METRICS") == "true" {
237+
prometheus.MustRegister(workloadCritical)
238+
prometheus.MustRegister(workloadHigh)
239+
prometheus.MustRegister(workloadMedium)
240+
prometheus.MustRegister(workloadLow)
241+
prometheus.MustRegister(workloadUnknown)
242+
prometheus.MustRegister(workloadVulnCritical)
243+
prometheus.MustRegister(workloadVulnHigh)
244+
prometheus.MustRegister(workloadVulnMedium)
245+
prometheus.MustRegister(workloadVulnLow)
246+
prometheus.MustRegister(workloadVulnUnknown)
247+
prometheus.MustRegister(workloadVulnCriticalRelevant)
248+
prometheus.MustRegister(workloadVulnHighRelevant)
249+
prometheus.MustRegister(workloadVulnMediumRelevant)
250+
prometheus.MustRegister(workloadVulnLowRelevant)
251+
prometheus.MustRegister(workloadVulnUnknownRelevant)
252+
}
159253
prometheus.MustRegister(namespaceCritical)
160254
prometheus.MustRegister(namespaceHigh)
161255
prometheus.MustRegister(namespaceMedium)
@@ -188,6 +282,32 @@ func init() {
188282
prometheus.MustRegister(clusterVulnUnknownRelevant)
189283
}
190284

285+
func ProcessConfigscanWorkloadMetrics(summary *v1beta1.WorkloadConfigurationScanSummaryList) {
286+
for _, item := range summary.Items {
287+
namespace := item.ObjectMeta.Labels["kubescape.io/workload-namespace"]
288+
workload := item.ObjectMeta.Labels["kubescape.io/workload-name"]
289+
kind := strings.ToLower(item.ObjectMeta.Labels["kubescape.io/workload-kind"])
290+
291+
workloadCritical.WithLabelValues(namespace, workload, kind).Set(float64(item.Spec.Severities.Critical))
292+
workloadHigh.WithLabelValues(namespace, workload, kind).Set(float64(item.Spec.Severities.High))
293+
workloadLow.WithLabelValues(namespace, workload, kind).Set(float64(item.Spec.Severities.Low))
294+
workloadMedium.WithLabelValues(namespace, workload, kind).Set(float64(item.Spec.Severities.Medium))
295+
workloadUnknown.WithLabelValues(namespace, workload, kind).Set(float64(item.Spec.Severities.Unknown))
296+
}
297+
}
298+
299+
func DeleteConfigscanWorkloadMetric(item *v1beta1.WorkloadConfigurationScanSummary) {
300+
namespace := item.ObjectMeta.Labels["kubescape.io/workload-namespace"]
301+
workload := item.ObjectMeta.Labels["kubescape.io/workload-name"]
302+
kind := strings.ToLower(item.ObjectMeta.Labels["kubescape.io/workload-kind"])
303+
304+
workloadCritical.DeleteLabelValues(namespace, workload, kind)
305+
workloadHigh.DeleteLabelValues(namespace, workload, kind)
306+
workloadMedium.DeleteLabelValues(namespace, workload, kind)
307+
workloadLow.DeleteLabelValues(namespace, workload, kind)
308+
workloadUnknown.DeleteLabelValues(namespace, workload, kind)
309+
}
310+
191311
func ProcessConfigscanNamespaceMetrics(summary *v1beta1.ConfigurationScanSummaryList) {
192312
for _, item := range summary.Items {
193313
namespace := item.ObjectMeta.Name
@@ -218,6 +338,44 @@ func ProcessConfigscanClusterMetrics(summary *v1beta1.ConfigurationScanSummaryLi
218338
return totalCritical, totalHigh, totalMedium, totalLow, totalUnknown
219339
}
220340

341+
func ProcessVulnWorkloadMetrics(summary *v1beta1.VulnerabilityManifestSummaryList) {
342+
for _, item := range summary.Items {
343+
namespace := item.ObjectMeta.Labels["kubescape.io/workload-namespace"]
344+
workload := item.ObjectMeta.Labels["kubescape.io/workload-name"]
345+
kind := strings.ToLower(item.ObjectMeta.Labels["kubescape.io/workload-kind"])
346+
containerName := strings.ToLower(item.ObjectMeta.Labels["kubescape.io/workload-container-name"])
347+
348+
workloadVulnCritical.WithLabelValues(namespace, workload, kind, containerName).Set(float64(item.Spec.Severities.Critical.All))
349+
workloadVulnHigh.WithLabelValues(namespace, workload, kind, containerName).Set(float64(item.Spec.Severities.High.All))
350+
workloadVulnMedium.WithLabelValues(namespace, workload, kind, containerName).Set(float64(item.Spec.Severities.Medium.All))
351+
workloadVulnLow.WithLabelValues(namespace, workload, kind, containerName).Set(float64(item.Spec.Severities.Low.All))
352+
workloadVulnUnknown.WithLabelValues(namespace, workload, kind, containerName).Set(float64(item.Spec.Severities.Unknown.All))
353+
workloadVulnCriticalRelevant.WithLabelValues(namespace, workload, kind, containerName).Set(float64(item.Spec.Severities.Critical.Relevant))
354+
workloadVulnHighRelevant.WithLabelValues(namespace, workload, kind, containerName).Set(float64(item.Spec.Severities.High.Relevant))
355+
workloadVulnMediumRelevant.WithLabelValues(namespace, workload, kind, containerName).Set(float64(item.Spec.Severities.Medium.Relevant))
356+
workloadVulnLowRelevant.WithLabelValues(namespace, workload, kind, containerName).Set(float64(item.Spec.Severities.Low.Relevant))
357+
workloadVulnUnknownRelevant.WithLabelValues(namespace, workload, kind, containerName).Set(float64(item.Spec.Severities.Unknown.Relevant))
358+
}
359+
}
360+
361+
func DeleteVulnWorkloadMetric(item *v1beta1.VulnerabilityManifestSummary) {
362+
namespace := item.ObjectMeta.Labels["kubescape.io/workload-namespace"]
363+
workload := item.ObjectMeta.Labels["kubescape.io/workload-name"]
364+
kind := strings.ToLower(item.ObjectMeta.Labels["kubescape.io/workload-kind"])
365+
containerName := strings.ToLower(item.ObjectMeta.Labels["kubescape.io/workload-container-name"])
366+
367+
workloadVulnCritical.DeleteLabelValues(namespace, workload, kind, containerName)
368+
workloadVulnHigh.DeleteLabelValues(namespace, workload, kind, containerName)
369+
workloadVulnMedium.DeleteLabelValues(namespace, workload, kind, containerName)
370+
workloadVulnLow.DeleteLabelValues(namespace, workload, kind, containerName)
371+
workloadVulnUnknown.DeleteLabelValues(namespace, workload, kind, containerName)
372+
workloadVulnCriticalRelevant.DeleteLabelValues(namespace, workload, kind, containerName)
373+
workloadVulnHighRelevant.DeleteLabelValues(namespace, workload, kind, containerName)
374+
workloadVulnMediumRelevant.DeleteLabelValues(namespace, workload, kind, containerName)
375+
workloadVulnLowRelevant.DeleteLabelValues(namespace, workload, kind, containerName)
376+
workloadVulnUnknownRelevant.DeleteLabelValues(namespace, workload, kind, containerName)
377+
}
378+
221379
func ProcessVulnNamespaceMetrics(summary *v1beta1.VulnerabilitySummaryList) {
222380
for _, item := range summary.Items {
223381
namespace := item.ObjectMeta.Name

0 commit comments

Comments
 (0)