Skip to content
Closed
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
14 changes: 9 additions & 5 deletions central/config/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,18 @@
}

// New returns a new Service instance using the given DataStore.
func New(datastore datastore.DataStore) Service {
func New(datastore datastore.DataStore, ar customMetrics.Runner) Service {
return &serviceImpl{
datastore: datastore,
datastore: datastore,
aggregator: ar,
}
}

type serviceImpl struct {
v1.UnimplementedConfigServiceServer

datastore datastore.DataStore
datastore datastore.DataStore
aggregator customMetrics.Runner
}

// RegisterServiceServer registers this service with the given gRPC Server.
Expand Down Expand Up @@ -165,10 +167,11 @@
}
}

if err := customMetrics.ValidateConfiguration(
req.GetConfig().GetPrivateConfig().GetMetrics()); err != nil {
customMetricsCfg, err := s.aggregator.ValidateConfiguration(
req.GetConfig().GetPrivateConfig().GetMetrics())
if err != nil {
return nil, err
}

Check warning on line 174 in central/config/service/service.go

View check run for this annotation

Codecov / codecov/patch

central/config/service/service.go#L170-L174

Added lines #L170 - L174 were not covered by tests

// Store:

Expand All @@ -185,7 +188,8 @@
}
matcher.Singleton().SetRegexes(regexes)
go reprocessor.Singleton().RunReprocessor()
s.aggregator.Reconfigure(customMetricsCfg)

Check warning on line 192 in central/config/service/service.go

View check run for this annotation

Codecov / codecov/patch

central/config/service/service.go#L191-L192

Added lines #L191 - L192 were not covered by tests
return req.GetConfig(), nil
}

Expand Down
2 changes: 1 addition & 1 deletion central/config/service/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func (s *configServiceTestSuite) SetupSuite() {
s.ctx = sac.WithAllAccess(context.Background())
s.db = pgtest.ForT(s.T())
s.dataStore = datastore.NewForTest(s.T(), s.db.DB)
s.srv = New(s.dataStore)
s.srv = New(s.dataStore, nil)

// Not found because Singleton() was not called and default configuration was not initialize.
cfg, err := s.srv.GetVulnerabilityExceptionConfig(s.ctx, &v1.Empty{})
Expand Down
3 changes: 2 additions & 1 deletion central/config/service/singleton.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import (
"github.com/stackrox/rox/central/config/datastore"
"github.com/stackrox/rox/central/metrics/aggregator"
"github.com/stackrox/rox/pkg/sync"
)

Expand All @@ -12,7 +13,7 @@
)

func initialize() {
as = New(datastore.Singleton())
as = New(datastore.Singleton(), aggregator.Singleton())

Check warning on line 16 in central/config/service/singleton.go

View check run for this annotation

Codecov / codecov/patch

central/config/service/singleton.go#L16

Added line #L16 was not covered by tests
}

// Singleton provides the instance of the Service interface to register.
Expand Down
13 changes: 13 additions & 0 deletions central/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ import (
"github.com/stackrox/rox/central/jwt"
logimbueHandler "github.com/stackrox/rox/central/logimbue/handler"
metadataService "github.com/stackrox/rox/central/metadata/service"
customMetrics "github.com/stackrox/rox/central/metrics/aggregator"
"github.com/stackrox/rox/central/metrics/telemetry"
mitreService "github.com/stackrox/rox/central/mitre/service"
namespaceService "github.com/stackrox/rox/central/namespace/service"
Expand Down Expand Up @@ -381,6 +382,7 @@ func startServices() {
administrationUsageInjector.Singleton().Start()
gcp.Singleton().Start()
administrationEventHandler.Singleton().Start()
customMetrics.Singleton().Start()

if features.PlatformComponents.Enabled() {
platformReprocessor.Singleton().Start()
Expand Down Expand Up @@ -866,6 +868,16 @@ func customRoutes() (customRoutes []routes.CustomRoute) {
ServerHandler: certHandler.BackupCerts(listener.Singleton()),
Compression: true,
},
{
// User configured Prometheus metrics will be exposed on this path.
// The access is behind authorization because the metric label
// values may include sensitive data, such as deployment names and
// CVEs.
Route: "GET /metrics",
Authorizer: user.With(permissions.View(resources.Administration)),
ServerHandler: customMetrics.Singleton(),
Compression: true,
},
}
scannerDefinitionsRoute := "/api/extensions/scannerdefinitions"
// Only grant compression to well-known content types. It should capture files
Expand Down Expand Up @@ -962,6 +974,7 @@ func waitForTerminationSignal() {
{gcp.Singleton(), "GCP cloud credentials manager"},
{cloudSourcesManager.Singleton(), "cloud sources manager"},
{administrationEventHandler.Singleton(), "administration events handler"},
{customMetrics.Singleton(), "custom Prometheus metrics gatherer"},
}

stoppables = append(stoppables,
Expand Down
16 changes: 15 additions & 1 deletion central/metrics/aggregator/common/tracker_base.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@
"sync/atomic"
"time"

"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
"github.com/stackrox/rox/central/metrics"
"github.com/stackrox/rox/generated/storage"
"github.com/stackrox/rox/pkg/logging"
"github.com/stackrox/rox/pkg/sync"
)

var log = logging.CreateLogger(logging.ModuleForName("central_metrics"), 1)
var (
log = logging.CreateLogger(logging.ModuleForName("central_metrics"), 1)
ErrStopIterator = errors.New("stopped")
)

type LazyLabel[Finding any] struct {
Label
Expand All @@ -31,6 +36,7 @@

type Tracker interface {
Run(context.Context)
ValidateConfiguration(*storage.PrometheusMetrics_MetricGroup) (*Configuration, error)
Reconfigure(context.Context, *Configuration)
}

Expand Down Expand Up @@ -87,6 +93,14 @@
return tracker
}

func (tracker *TrackerBase[Finding]) ValidateConfiguration(cfg *storage.PrometheusMetrics_MetricGroup) (*Configuration, error) {
current := tracker.GetConfiguration()
if current == nil {
current = &Configuration{}
}
return ValidateConfiguration(cfg, current.metrics, tracker.labelOrder)

Check warning on line 101 in central/metrics/aggregator/common/tracker_base.go

View check run for this annotation

Codecov / codecov/patch

central/metrics/aggregator/common/tracker_base.go#L96-L101

Added lines #L96 - L101 were not covered by tests
}

// Reconfigure assumes the configuration has been validated, so doesn't return
// an error.
func (tracker *TrackerBase[Finding]) Reconfigure(ctx context.Context, cfg *Configuration) {
Expand All @@ -100,7 +114,7 @@
// Force track only on reconfiguration, not on the initial tracker
// creation.
if tracker.running.Load() {
tracker.track(ctx)

Check warning on line 117 in central/metrics/aggregator/common/tracker_base.go

View check run for this annotation

Codecov / codecov/patch

central/metrics/aggregator/common/tracker_base.go#L117

Added line #L117 was not covered by tests
} else {
go tracker.Run(ctx)
}
Expand All @@ -120,10 +134,10 @@
}
}

func (tracker *TrackerBase[Finding]) unregisterMetric(metric MetricName) {
if metrics.UnregisterCustomAggregatedMetric(string(metric)) {
log.Debugf("Unregistered %s Prometheus metric %q", tracker.category, metric)
}

Check warning on line 140 in central/metrics/aggregator/common/tracker_base.go

View check run for this annotation

Codecov / codecov/patch

central/metrics/aggregator/common/tracker_base.go#L137-L140

Added lines #L137 - L140 were not covered by tests
}

func labelsAsStrings(labels []Label) []string {
Expand All @@ -141,9 +155,9 @@
cfg.period,
labelsAsStrings(cfg.metrics[metric]),
); err != nil {
log.Errorf("Failed to register %s metric %q: %v", tracker.category, metric, err)
return
}

Check warning on line 160 in central/metrics/aggregator/common/tracker_base.go

View check run for this annotation

Codecov / codecov/patch

central/metrics/aggregator/common/tracker_base.go#L158-L160

Added lines #L158 - L160 were not covered by tests
log.Debugf("Registered %s Prometheus metric %q", tracker.category, metric)
}

Expand Down Expand Up @@ -183,8 +197,8 @@
func (tracker *TrackerBase[Finding]) track(ctx context.Context) {
cfg := tracker.GetConfiguration()
if len(cfg.metrics) == 0 {
return
}

Check warning on line 201 in central/metrics/aggregator/common/tracker_base.go

View check run for this annotation

Codecov / codecov/patch

central/metrics/aggregator/common/tracker_base.go#L200-L201

Added lines #L200 - L201 were not covered by tests
aggregator := makeAggregator(cfg.metrics, tracker.labelOrder, tracker.getters)
for finding := range tracker.generator(ctx, cfg.metrics) {
aggregator.count(finding)
Expand All @@ -209,8 +223,8 @@

// Return if no ticker or is already running.
if ticker == nil || !tracker.running.CompareAndSwap(false, true) {
return
}

Check warning on line 227 in central/metrics/aggregator/common/tracker_base.go

View check run for this annotation

Codecov / codecov/patch

central/metrics/aggregator/common/tracker_base.go#L226-L227

Added lines #L226 - L227 were not covered by tests
defer tracker.running.Store(false)
defer ticker.Stop()

Expand All @@ -218,8 +232,8 @@

for {
select {
case <-ticker.C:
tracker.track(ctx)

Check warning on line 236 in central/metrics/aggregator/common/tracker_base.go

View check run for this annotation

Codecov / codecov/patch

central/metrics/aggregator/common/tracker_base.go#L235-L236

Added lines #L235 - L236 were not covered by tests
case <-ctx.Done():
return
}
Expand Down
36 changes: 33 additions & 3 deletions central/metrics/aggregator/image_vulnerabilities/labels.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,53 @@
package image_vulnerabilities

import (
"strconv"

"github.com/stackrox/rox/central/metrics/aggregator/common"
"github.com/stackrox/rox/central/platform/matcher"
"github.com/stackrox/rox/generated/storage"
)

var (
lazyLabels = []common.LazyLabel[finding]{
{Label: "Cluster", Getter: func(f *finding) string { return f.deployment.GetClusterName() }},
{Label: "Namespace", Getter: func(f *finding) string { return f.deployment.GetNamespace() }},
{Label: "Deployment", Getter: func(f *finding) string { return f.deployment.GetName() }},
{Label: "IsPlatformWorkload", Getter: isPlatformWorkload},

{Label: "ImageID", Getter: func(f *finding) string { return f.image.GetId() }},
{Label: "ImageRegistry", Getter: func(f *finding) string { return f.name.GetRegistry() }},
{Label: "ImageRemote", Getter: func(f *finding) string { return f.name.GetRemote() }},

Check warning on line 20 in central/metrics/aggregator/image_vulnerabilities/labels.go

View check run for this annotation

Codecov / codecov/patch

central/metrics/aggregator/image_vulnerabilities/labels.go#L18-L20

Added lines #L18 - L20 were not covered by tests
{Label: "ImageTag", Getter: func(f *finding) string { return f.name.GetTag() }},
{Label: "Component", Getter: func(f *finding) string { return f.component.GetName() }},
{Label: "ComponentVersion", Getter: func(f *finding) string { return f.component.GetVersion() }},
{Label: "OperatingSystem", Getter: func(f *finding) string { return f.image.GetScan().GetOperatingSystem() }},

Check warning on line 24 in central/metrics/aggregator/image_vulnerabilities/labels.go

View check run for this annotation

Codecov / codecov/patch

central/metrics/aggregator/image_vulnerabilities/labels.go#L22-L24

Added lines #L22 - L24 were not covered by tests

{Label: "CVE", Getter: func(f *finding) string { return f.vuln.GetCve() }},
{Label: "CVSS", Getter: func(f *finding) string { return strconv.FormatFloat(float64(f.vuln.GetCvss()), 'f', 1, 32) }},

Check warning on line 27 in central/metrics/aggregator/image_vulnerabilities/labels.go

View check run for this annotation

Codecov / codecov/patch

central/metrics/aggregator/image_vulnerabilities/labels.go#L26-L27

Added lines #L26 - L27 were not covered by tests
{Label: "Severity", Getter: func(f *finding) string { return f.vuln.GetSeverity().String() }},
{Label: "SeverityV2", Getter: func(f *finding) string { return f.vuln.GetCvssV2().GetSeverity().String() }},
{Label: "SeverityV3", Getter: func(f *finding) string { return f.vuln.GetCvssV3().GetSeverity().String() }},
{Label: "IsFixable", Getter: func(f *finding) string { return strconv.FormatBool(f.vuln.GetFixedBy() != "") }},

Check warning on line 31 in central/metrics/aggregator/image_vulnerabilities/labels.go

View check run for this annotation

Codecov / codecov/patch

central/metrics/aggregator/image_vulnerabilities/labels.go#L29-L31

Added lines #L29 - L31 were not covered by tests
}

labels = common.MakeLabelOrderMap(lazyLabels)
order = common.MakeLabelOrderMap(lazyLabels)
)

type finding struct {
deployment *storage.Deployment
image *storage.Image
name *storage.ImageName
component *storage.EmbeddedImageScanComponent
vuln *storage.EmbeddedVulnerability
}

func isPlatformWorkload(f *finding) string {
p, _ := matcher.Singleton().MatchDeployment(f.deployment)
return strconv.FormatBool(p)

Check warning on line 47 in central/metrics/aggregator/image_vulnerabilities/labels.go

View check run for this annotation

Codecov / codecov/patch

central/metrics/aggregator/image_vulnerabilities/labels.go#L45-L47

Added lines #L45 - L47 were not covered by tests
}

func ValidateConfiguration(config map[string]*storage.PrometheusMetrics_MetricGroup_Labels) error {
_, err := common.TranslateMetricLabels(config, labels)
func ValidateConfiguration(metricLabels map[string]*storage.PrometheusMetrics_MetricGroup_Labels) error {
_, err := common.TranslateMetricLabels(metricLabels, order)
return err

Check warning on line 52 in central/metrics/aggregator/image_vulnerabilities/labels.go

View check run for this annotation

Codecov / codecov/patch

central/metrics/aggregator/image_vulnerabilities/labels.go#L50-L52

Added lines #L50 - L52 were not covered by tests
}
57 changes: 57 additions & 0 deletions central/metrics/aggregator/image_vulnerabilities/tracker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package image_vulnerabilities

import (
"context"
"iter"

"github.com/prometheus/client_golang/prometheus"
deploymentDS "github.com/stackrox/rox/central/deployment/datastore"
"github.com/stackrox/rox/central/metrics/aggregator/common"
"github.com/stackrox/rox/generated/storage"
"github.com/stackrox/rox/pkg/search"
)

func New(gauge func(string, prometheus.Labels, int), ds deploymentDS.DataStore) *common.TrackerBase[finding] {
return common.MakeTrackerBase(
"vulnerabilities",
"aggregated CVEs",
lazyLabels,
func(ctx context.Context, mcfg common.MetricsConfiguration) iter.Seq[*finding] {
return trackVulnerabilityMetrics(ctx, mcfg, ds)
},
gauge)
}

func trackVulnerabilityMetrics(ctx context.Context, _ common.MetricsConfiguration, ds deploymentDS.DataStore) iter.Seq[*finding] {
return func(yield func(*finding) bool) {
finding := &finding{}
_ = ds.WalkByQuery(ctx, search.EmptyQuery(), func(deployment *storage.Deployment) error {
finding.deployment = deployment
images, err := ds.GetImagesForDeployment(ctx, deployment)
if err != nil {
return nil // Nothing can be done with this error here.
}

Check warning on line 33 in central/metrics/aggregator/image_vulnerabilities/tracker.go

View check run for this annotation

Codecov / codecov/patch

central/metrics/aggregator/image_vulnerabilities/tracker.go#L32-L33

Added lines #L32 - L33 were not covered by tests
for _, finding.image = range images {
if !forEachImageVuln(yield, finding) {
return common.ErrStopIterator
}

Check warning on line 37 in central/metrics/aggregator/image_vulnerabilities/tracker.go

View check run for this annotation

Codecov / codecov/patch

central/metrics/aggregator/image_vulnerabilities/tracker.go#L36-L37

Added lines #L36 - L37 were not covered by tests
}
return nil
})
}
}

// forEachImageVuln yields a finding for every vulnerability associated with
// each image name.
func forEachImageVuln(yield func(*finding) bool, f *finding) bool {
for _, f.component = range f.image.GetScan().GetComponents() {
for _, f.vuln = range f.component.GetVulns() {
for _, f.name = range f.image.GetNames() {
if !yield(f) {
return false
}
}
}
}
return true
}
Loading
Loading