Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion central/metrics/custom/image_vulnerabilities/tracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
func New(registry metrics.CustomRegistry, ds deploymentDS.DataStore) *tracker.TrackerBase[*finding] {
return tracker.MakeTrackerBase(
"vulnerabilities",
"aggregated CVEs",
"CVEs",
lazyLabels,
func(ctx context.Context, md tracker.MetricDescriptors) iter.Seq[*finding] {
return trackVulnerabilityMetrics(ctx, md, ds)
Expand Down
56 changes: 56 additions & 0 deletions central/metrics/custom/policy_violations/labels.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package policy_violations

import (
"slices"
"strconv"
"strings"

"github.com/stackrox/rox/central/metrics/custom/tracker"
"github.com/stackrox/rox/generated/storage"
)

var lazyLabels = []tracker.LazyLabel[*finding]{
// Alert
{Label: "Cluster", Getter: func(f *finding) string { return f.GetClusterName() }},
{Label: "Namespace", Getter: func(f *finding) string { return f.GetNamespace() }},
{Label: "Resource", Getter: func(f *finding) string { return f.GetResource().GetName() }},
{Label: "Deployment", Getter: func(f *finding) string { return f.GetDeployment().GetName() }},
{Label: "IsDeploymentActive", Getter: func(f *finding) string { return strconv.FormatBool(!f.GetDeployment().GetInactive()) }},
{Label: "IsPlatformComponent", Getter: func(f *finding) string { return strconv.FormatBool(f.GetPlatformComponent()) }},
{Label: "Policy", Getter: func(f *finding) string { return f.GetPolicy().GetName() }},
{Label: "Categories", Getter: func(f *finding) string {
return strings.Join(slices.Sorted(slices.Values(f.GetPolicy().GetCategories())), ",")
}},
{Label: "Severity", Getter: func(f *finding) string { return f.GetPolicy().GetSeverity().String() }},
{Label: "Action", Getter: func(f *finding) string { return f.GetEnforcement().GetAction().String() }},
{Label: "Message", Getter: func(f *finding) string { return f.GetEnforcement().GetMessage() }},
{Label: "Stage", Getter: func(f *finding) string { return f.GetLifecycleStage().String() }},
{Label: "State", Getter: func(f *finding) string { return f.GetState().String() }},
{Label: "Entity", Getter: func(f *finding) string { return f.GetEntityType().String() }},
{Label: "EntityName", Getter: getEntityName},

// Violation
{Label: "Type", Getter: func(f *finding) string { return f.GetType().String() }},
}

type finding struct {
err error
*storage.Alert
*storage.Alert_Violation
}

func (f *finding) GetError() error {
return f.err
}

func getEntityName(f *finding) string {
switch e := f.GetEntity().(type) {
case *storage.Alert_Deployment_:
return e.Deployment.GetName()
case *storage.Alert_Image:
return e.Image.GetName().GetFullName()
case *storage.Alert_Resource_:
return e.Resource.GetName()
}
return ""
}
39 changes: 39 additions & 0 deletions central/metrics/custom/policy_violations/tracker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package policy_violations

import (
"context"
"iter"

alertDS "github.com/stackrox/rox/central/alert/datastore"
"github.com/stackrox/rox/central/metrics"
"github.com/stackrox/rox/central/metrics/custom/tracker"
"github.com/stackrox/rox/generated/storage"
"github.com/stackrox/rox/pkg/search"
)

func New(registry metrics.CustomRegistry, ds alertDS.DataStore) *tracker.TrackerBase[*finding] {
return tracker.MakeTrackerBase(
"alerts",
"policy violations",
lazyLabels,
func(ctx context.Context, _ tracker.MetricDescriptors) iter.Seq[*finding] {
return trackViolations(ctx, ds)
},
registry)
}

func trackViolations(ctx context.Context, ds alertDS.DataStore) iter.Seq[*finding] {
f := finding{}
return func(yield func(*finding) bool) {
_ = ds.WalkByQuery(ctx, search.EmptyQuery(), func(a *storage.Alert) error {
f.Alert = a
for _, v := range a.GetViolations() {
f.Alert_Violation = v
if !yield(&f) {
return tracker.ErrStopIterator
}
}
return nil
})
}
}
13 changes: 12 additions & 1 deletion central/metrics/custom/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import (
"context"
"net/http"

alertDS "github.com/stackrox/rox/central/alert/datastore"
configDS "github.com/stackrox/rox/central/config/datastore"
deploymentDS "github.com/stackrox/rox/central/deployment/datastore"
"github.com/stackrox/rox/central/metrics"
"github.com/stackrox/rox/central/metrics/custom/image_vulnerabilities"
"github.com/stackrox/rox/central/metrics/custom/policy_violations"
custom "github.com/stackrox/rox/central/metrics/custom/tracker"
"github.com/stackrox/rox/generated/storage"
"github.com/stackrox/rox/pkg/grpc/authn"
Expand All @@ -18,6 +20,7 @@ import (
type aggregatorRunner struct {
registry metrics.CustomRegistry
image_vulnerabilities custom.Tracker
policy_violations custom.Tracker
}

// RunnerConfiguration is a composition of tracker configurations.
Expand All @@ -26,12 +29,14 @@ type aggregatorRunner struct {
// any changes.
type RunnerConfiguration struct {
image_vulnerabilities *custom.Configuration
policy_violations *custom.Configuration
}

func makeRunner(registry metrics.CustomRegistry, dds deploymentDS.DataStore) *aggregatorRunner {
func makeRunner(registry metrics.CustomRegistry, dds deploymentDS.DataStore, ads alertDS.DataStore) *aggregatorRunner {
return &aggregatorRunner{
registry: registry,
image_vulnerabilities: image_vulnerabilities.New(registry, dds),
policy_violations: policy_violations.New(registry, ads),
}
}

Expand Down Expand Up @@ -61,6 +66,10 @@ func (ar *aggregatorRunner) ValidateConfiguration(cfg *storage.PrometheusMetrics
if err != nil {
return nil, err
}
runnerConfig.policy_violations, err = ar.policy_violations.NewConfiguration(cfg.GetPolicyViolations())
if err != nil {
return nil, err
}
return runnerConfig, nil
}

Expand All @@ -74,6 +83,7 @@ func (ar *aggregatorRunner) Reconfigure(cfg *RunnerConfiguration) {
log.Panic("programmer error: nil configuration passed")
} else {
ar.image_vulnerabilities.Reconfigure(cfg.image_vulnerabilities)
ar.policy_violations.Reconfigure(cfg.policy_violations)
}
}

Expand All @@ -85,6 +95,7 @@ func (ar *aggregatorRunner) ServeHTTP(w http.ResponseWriter, req *http.Request)
// The request context is cancelled when the client's connection closes.
ctx := authn.CopyContextIdentity(context.Background(), req.Context())
go ar.image_vulnerabilities.Gather(ctx)
go ar.policy_violations.Gather(ctx)
}
ar.registry.Lock()
defer ar.registry.Unlock()
Expand Down
55 changes: 46 additions & 9 deletions central/metrics/custom/runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ package custom

import (
"context"
"fmt"
"io"
"net/http/httptest"
"testing"

"github.com/pkg/errors"
alertDS "github.com/stackrox/rox/central/alert/datastore/mocks"
configDS "github.com/stackrox/rox/central/config/datastore/mocks"
deploymentDS "github.com/stackrox/rox/central/deployment/datastore/mocks"
"github.com/stackrox/rox/central/metrics"
Expand All @@ -28,7 +30,7 @@ func TestRunner_makeRunner(t *testing.T) {
Metrics: nil,
},
nil)
runner := makeRunner(metrics.MakeCustomRegistry(), nil)
runner := makeRunner(metrics.MakeCustomRegistry(), nil, nil)
runner.initialize(cds)
assert.NotNil(t, runner)

Expand All @@ -52,7 +54,7 @@ func TestRunner_makeRunner(t *testing.T) {
cds.EXPECT().GetPrivateConfig(gomock.Any()).Times(1).Return(
nil,
errors.New("DB error"))
runner := makeRunner(metrics.MakeCustomRegistry(), nil)
runner := makeRunner(metrics.MakeCustomRegistry(), nil, nil)
assert.NotNil(t, runner)
runner.initialize(cds)

Expand Down Expand Up @@ -86,6 +88,13 @@ func TestRunner_ServeHTTP(t *testing.T) {
"test_metric": {
Labels: []string{"Cluster", "Severity"},
},
}},
PolicyViolations: &storage.PrometheusMetrics_Group{
GatheringPeriodMinutes: 10,
Descriptors: map[string]*storage.PrometheusMetrics_Group_Labels{
"test_violations_metric": {
Labels: []string{"Cluster", "Policy", "Categories"},
},
}}}},
nil)

Expand Down Expand Up @@ -113,19 +122,40 @@ func TestRunner_ServeHTTP(t *testing.T) {
}},
}, nil)

runner := makeRunner(metrics.MakeCustomRegistry(), dds)
ads := alertDS.NewMockDataStore(ctrl)

ads.EXPECT().WalkByQuery(gomock.Any(), gomock.Any(), gomock.Any()).
Times(1).
Do(func(_ context.Context, _ *v1.Query, f func(*storage.Alert) error) {
_ = f(&storage.Alert{
ClusterName: "cluster1",
Violations: []*storage.Alert_Violation{
{
Message: "violation",
},
},
Policy: &storage.Policy{
Name: "Test Policy",
Categories: []string{"catB", "catA"},
},
})
}).
Return(nil)

runner := makeRunner(metrics.MakeCustomRegistry(), dds, ads)
runner.initialize(cds)
runner.image_vulnerabilities.Gather(makeAdminContext(t))
runner.policy_violations.Gather(makeAdminContext(t))

expectedBody := func(metricName string) string {
return `# HELP rox_central_` + metricName + ` The total number of aggregated CVEs aggregated by Cluster,Severity and gathered every 10m0s` + "\n" +
`# TYPE rox_central_` + metricName + ` gauge` + "\n" +
`rox_central_` + metricName + `{Cluster="cluster1",Severity="IMPORTANT_VULNERABILITY_SEVERITY"} 1` + "\n"
expectedBody := func(metricName, decription, labels, vector string) string {
metricName = "rox_central_" + metricName
return fmt.Sprintf("# HELP %s The total number of %s aggregated by %s and gathered every 10m0s\n"+
"# TYPE %s gauge\n%s{%s} 1\n", metricName, decription, labels, metricName, metricName, vector)
}

t.Run("body", func(t *testing.T) {
rec := httptest.NewRecorder()
req := httptest.NewRequestWithContext(makeAdminContext(t),
req := httptest.NewRequestWithContext(context.Background(),
"GET", "/metrics", nil)
runner.ServeHTTP(rec, req)

Expand All @@ -134,7 +164,14 @@ func TestRunner_ServeHTTP(t *testing.T) {
body, err := io.ReadAll(result.Body)
_ = result.Body.Close()
assert.NoError(t, err)
assert.Equal(t, expectedBody("test_metric"), string(body))
assert.Contains(t, string(body),
expectedBody("test_metric", "CVEs",
"Cluster,Severity",
`Cluster="cluster1",Severity="IMPORTANT_VULNERABILITY_SEVERITY"`))
assert.Contains(t, string(body),
expectedBody("test_violations_metric", "policy violations",
"Cluster,Policy,Categories",
`Categories="catA,catB",Cluster="cluster1",Policy="Test Policy"`))
})
}

Expand Down
3 changes: 2 additions & 1 deletion central/metrics/custom/singleton.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package custom
import (
"net/http"

alertDS "github.com/stackrox/rox/central/alert/datastore"
configDS "github.com/stackrox/rox/central/config/datastore"
deploymentDS "github.com/stackrox/rox/central/deployment/datastore"
"github.com/stackrox/rox/central/metrics"
Expand All @@ -28,7 +29,7 @@ type Runner interface {
// initialization. nil runner is safe, but no-op.
func Singleton() Runner {
onceRunner.Do(func() {
runner = makeRunner(metrics.MakeCustomRegistry(), deploymentDS.Singleton())
runner = makeRunner(metrics.MakeCustomRegistry(), deploymentDS.Singleton(), alertDS.Singleton())
go runner.initialize(configDS.Singleton())
})
return runner
Expand Down
5 changes: 4 additions & 1 deletion generated/api/v1/config_service.swagger.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 34 additions & 24 deletions generated/storage/config.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading