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
15 changes: 9 additions & 6 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,8 +167,9 @@
}
}

if err := customMetrics.ParseConfiguration(
req.GetConfig().GetPrivateConfig().GetPrometheusMetricsConfig()); err != nil {
customMetricsCfg, err := s.aggregator.ParseConfiguration(
req.GetConfig().GetPrivateConfig().GetPrometheusMetricsConfig())
if err != nil {

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

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L170 - L172 were not covered by tests
return nil, err
}

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

s.aggregator.Reconfigure(customMetricsCfg)

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

View check run for this annotation

Codecov / codecov/patch

central/config/service/service.go#L191

Added line #L191 was 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
21 changes: 20 additions & 1 deletion 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,7 +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 +867,23 @@ 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,
},
{
// Same as above, for custom paths.
Route: "GET /metrics/{custom}",
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 +980,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
21 changes: 20 additions & 1 deletion central/metrics/aggregator/common/config_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,25 @@
exposure metrics.Exposure
}

func (me *metricExposure) String() string {
switch me.exposure {
case metrics.INTERNAL:
return "internal path /metrics"
case metrics.EXTERNAL:
if me.registry == "" {
return "external path /metrics"
}
return "external path /metrics/" + me.registry
case metrics.BOTH:
if me.registry == "" {
return "internal and external path /metrics"
}
return "internal path /metrics and external path /metrics/" + me.registry
default:
return "not exposed"

Check warning on line 46 in central/metrics/aggregator/common/config_parser.go

View check run for this annotation

Codecov / codecov/patch

central/metrics/aggregator/common/config_parser.go#L31-L46

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

type Configuration struct {
metrics MetricsConfiguration
metricRegistry map[MetricName]metricExposure
Expand Down Expand Up @@ -95,7 +114,7 @@
return result, nil
}

func ParseConfiguration(cfg *storage.PrometheusMetricsConfig_Metrics, currentMetrics MetricsConfiguration, labelOrder map[Label]int) (*Configuration, error) {
func parseConfiguration(cfg *storage.PrometheusMetricsConfig_Metrics, currentMetrics MetricsConfiguration, labelOrder map[Label]int) (*Configuration, error) {
metricRegistry := make(map[MetricName]metricExposure, len(cfg.GetMetrics()))

for metric, labels := range cfg.GetMetrics() {
Expand Down
20 changes: 10 additions & 10 deletions central/metrics/aggregator/common/config_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func Test_parseErrors(t *testing.T) {

func TestParseConfiguration(t *testing.T) {
t.Run("bad metric name", func(t *testing.T) {
cfg, err := ParseConfiguration(&storage.PrometheusMetricsConfig_Metrics{
cfg, err := parseConfiguration(&storage.PrometheusMetricsConfig_Metrics{
GatheringPeriodMinutes: 121,
Metrics: map[string]*storage.PrometheusMetricsConfig_Labels{
" ": nil,
Expand All @@ -103,7 +103,7 @@ func TestParseConfiguration(t *testing.T) {
})

t.Run("bad registry name", func(t *testing.T) {
cfg, err := ParseConfiguration(&storage.PrometheusMetricsConfig_Metrics{
cfg, err := parseConfiguration(&storage.PrometheusMetricsConfig_Metrics{
GatheringPeriodMinutes: 121,
Metrics: map[string]*storage.PrometheusMetricsConfig_Labels{
"m1": {
Expand All @@ -119,7 +119,7 @@ func TestParseConfiguration(t *testing.T) {

t.Run("test parse sequence", func(t *testing.T) {
// Good:
cfg0, err := ParseConfiguration(&storage.PrometheusMetricsConfig_Metrics{
cfg0, err := parseConfiguration(&storage.PrometheusMetricsConfig_Metrics{
GatheringPeriodMinutes: 121,
Metrics: makeTestMetricLabels(t),
Filter: "Cluster:name",
Expand All @@ -132,7 +132,7 @@ func TestParseConfiguration(t *testing.T) {
}

// Bad:
cfg1, err := ParseConfiguration(&storage.PrometheusMetricsConfig_Metrics{
cfg1, err := parseConfiguration(&storage.PrometheusMetricsConfig_Metrics{
GatheringPeriodMinutes: 121,
Metrics: map[string]*storage.PrometheusMetricsConfig_Labels{
"m1": {
Expand All @@ -147,7 +147,7 @@ func TestParseConfiguration(t *testing.T) {
assert.Nil(t, cfg1)

// Another good:
cfg2, err := ParseConfiguration(&storage.PrometheusMetricsConfig_Metrics{
cfg2, err := parseConfiguration(&storage.PrometheusMetricsConfig_Metrics{
GatheringPeriodMinutes: 121,
Metrics: map[string]*storage.PrometheusMetricsConfig_Labels{
"m2": {
Expand All @@ -168,7 +168,7 @@ func TestParseConfiguration(t *testing.T) {
})

t.Run("test bad query", func(t *testing.T) {
cfg, err := ParseConfiguration(&storage.PrometheusMetricsConfig_Metrics{
cfg, err := parseConfiguration(&storage.PrometheusMetricsConfig_Metrics{
GatheringPeriodMinutes: 121,
Metrics: makeTestMetricLabels(t),
Filter: "bad query?",
Expand All @@ -185,7 +185,7 @@ func TestParseConfiguration(t *testing.T) {
GatheringPeriodMinutes: 121,
Metrics: makeTestMetricLabels(t),
}
cfg0, err := ParseConfiguration(storageConfig, nil, testLabelOrder)
cfg0, err := parseConfiguration(storageConfig, nil, testLabelOrder)
assert.NoError(t, err)

for _, labels := range storageConfig.Metrics {
Expand All @@ -207,7 +207,7 @@ func TestParseConfiguration(t *testing.T) {
}
}

cfg1, err := ParseConfiguration(storageConfig, cfg0.metrics, testLabelOrder)
cfg1, err := parseConfiguration(storageConfig, cfg0.metrics, testLabelOrder)

assert.ErrorIs(t, err, errInvalidConfiguration, err)
assert.Nil(t, cfg1)
Expand All @@ -218,14 +218,14 @@ func TestParseConfiguration(t *testing.T) {
GatheringPeriodMinutes: 121,
Metrics: makeTestMetricLabels(t),
}
cfg0, err := ParseConfiguration(storageConfig, nil, testLabelOrder)
cfg0, err := parseConfiguration(storageConfig, nil, testLabelOrder)
assert.NoError(t, err)
for _, config := range storageConfig.Metrics {
if config.Exposure == storage.PrometheusMetricsConfig_Labels_BOTH {
config.Labels["CVE"] = &storage.PrometheusMetricsConfig_Labels_Expression{}
}
}
cfg1, err := ParseConfiguration(storageConfig, cfg0.metrics, testLabelOrder)
cfg1, err := parseConfiguration(storageConfig, cfg0.metrics, testLabelOrder)

assert.ErrorIs(t, err, errInvalidConfiguration, err)
assert.True(t, strings.Contains(err.Error(), "cannot alter metrics"))
Expand Down
30 changes: 25 additions & 5 deletions central/metrics/aggregator/common/tracker_base.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,23 @@
"sync/atomic"
"time"

"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
"github.com/stackrox/rox/central/metrics"
v1 "github.com/stackrox/rox/generated/api/v1"
"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 Tracker interface {
Run(context.Context)
ParseConfiguration(*storage.PrometheusMetricsConfig_Metrics) (*Configuration, error)
Reconfigure(context.Context, *Configuration)
}

Expand Down Expand Up @@ -88,6 +94,14 @@
return tracker
}

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

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

View check run for this annotation

Codecov / codecov/patch

central/metrics/aggregator/common/tracker_base.go#L97-L102

Added lines #L97 - L102 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 Down Expand Up @@ -135,11 +149,15 @@
cfg.period,
getMetricLabels(cfg.metrics[metric], tracker.labelOrder),
regCfg.registry,
metrics.Exposure(regCfg.exposure)); err != nil {
log.Errorf("Failed to register %s metric %q: %v", tracker.category, metric, err)
regCfg.exposure); err != nil {
log.Errorf("Failed to register %s metric %q (%s): %v",
tracker.category, metric, &regCfg, err)

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

View check run for this annotation

Codecov / codecov/patch

central/metrics/aggregator/common/tracker_base.go#L153-L154

Added lines #L153 - L154 were not covered by tests
} else if regCfg.exposure >= metrics.INTERNAL && regCfg.exposure <= metrics.BOTH {
log.Debugf("Registered %s Prometheus metric %q (%s)",
tracker.category, metric, &regCfg)

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

View check run for this annotation

Codecov / codecov/patch

central/metrics/aggregator/common/tracker_base.go#L156-L157

Added lines #L156 - L157 were not covered by tests
} else {
log.Debugf("Registered %s Prometheus metric %q on path /metrics/%s", tracker.category, metric,
regCfg.registry)
log.Debugf("Ignored %s Prometheus metric %q (%s)",
tracker.category, metric, &regCfg)
}
}

Expand Down Expand Up @@ -181,11 +199,13 @@
if len(cfg.metrics) == 0 {
return
}
log.Debugf("starting %s metrics gathering...", tracker.category)
aggregator := makeAggregator(cfg.metrics, tracker.labelOrder, tracker.getters)
for finding := range tracker.generator(ctx, cfg.filter, cfg.metrics) {
aggregator.count(finding)
}
for metric, records := range aggregator.result {
log.Debugf("updating %s metric %s", tracker.category, metric)
for _, rec := range records {
tracker.gauge(string(metric), rec.labels, rec.total)
}
Expand Down
35 changes: 20 additions & 15 deletions central/metrics/aggregator/image_vulnerabilities/labels.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
package image_vulnerabilities

import (
"strconv"

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

var (
getters = []common.LabelGetter[*finding]{
{Label: "Cluster", Getter: func(f *finding) string { return f.deployment.GetClusterName() }},
}

labels = common.MakeLabelOrderMap(getters)
)
var getters = []common.LabelGetter[*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},

type finding struct {
common.OneOrMore
deployment *storage.Deployment
}
{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 17 in central/metrics/aggregator/image_vulnerabilities/labels.go

View check run for this annotation

Codecov / codecov/patch

central/metrics/aggregator/image_vulnerabilities/labels.go#L15-L17

Added lines #L15 - L17 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 21 in central/metrics/aggregator/image_vulnerabilities/labels.go

View check run for this annotation

Codecov / codecov/patch

central/metrics/aggregator/image_vulnerabilities/labels.go#L19-L21

Added lines #L19 - L21 were not covered by tests

func ParseConfiguration(config *storage.PrometheusMetricsConfig_Metrics) error {
_, err := common.ParseConfiguration(config, nil, labels)
return err
{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 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#L23-L24

Added lines #L23 - L24 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 28 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-L28

Added lines #L26 - L28 were not covered by tests
}
Loading
Loading