Skip to content
Draft
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
12 changes: 9 additions & 3 deletions central/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ import (
"github.com/stackrox/rox/pkg/grpc/authz/or"
"github.com/stackrox/rox/pkg/grpc/authz/perrpc"
"github.com/stackrox/rox/pkg/grpc/authz/user"
"github.com/stackrox/rox/pkg/grpc/common/requestinterceptor"
"github.com/stackrox/rox/pkg/grpc/errors"
"github.com/stackrox/rox/pkg/grpc/ratelimit"
"github.com/stackrox/rox/pkg/grpc/routes"
Expand Down Expand Up @@ -634,10 +635,15 @@ func startGRPCServer() {
centralSAC.GetEnricher().GetPreAuthContextEnricher(authzTraceSink),
)

// Telemetry client has to add interceptors before starting the server.
// Single request interceptor computes RequestParams once and fans out to
// all registered handlers (telemetry, metrics, etc.).
ri := requestinterceptor.NewRequestInterceptor()
config.HTTPInterceptors = append(config.HTTPInterceptors, ri.HTTPInterceptor())
config.UnaryInterceptors = append(config.UnaryInterceptors, ri.UnaryServerInterceptor())
config.StreamInterceptors = append(config.StreamInterceptors, ri.StreamServerInterceptor())

c := phonehomeClient.Singleton()
config.HTTPInterceptors = append(config.HTTPInterceptors, c.GetHTTPInterceptor())
config.UnaryInterceptors = append(config.UnaryInterceptors, c.GetGRPCInterceptor())
ri.Add("telemetry", c.GetRequestHandler())

server := pkgGRPC.NewAPI(config)
server.Register(servicesToRegister()...)
Expand Down
9 changes: 2 additions & 7 deletions central/metrics/custom/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,7 @@ import (
"github.com/stackrox/rox/pkg/telemetry/phonehome/telemeter"
)

type trackerRunner []struct {
tracker.Tracker
// getGroupConfig returns the storage configuration associated to the
// tracker.
getGroupConfig func(*storage.PrometheusMetrics) *storage.PrometheusMetrics_Group
}
type trackerRunner []tracker.Registration

// RunnerConfiguration is a composition of tracker configurations.
// Returned by ValidateConfiguration() and accepted by Reconfigure(). This split
Expand Down Expand Up @@ -123,7 +118,7 @@ func (tr trackerRunner) ValidateConfiguration(cfg *storage.PrometheusMetrics) (R
}
var runnerConfig RunnerConfiguration
for _, tracker := range tr {
trackerConfig, err := tracker.NewConfiguration(tracker.getGroupConfig(cfg))
trackerConfig, err := tracker.NewConfiguration(tracker.GetGroupConfig(cfg))
if err != nil {
return nil, err
}
Expand Down
27 changes: 24 additions & 3 deletions central/metrics/custom/tracker/tracker_base.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,20 @@ type Getter[F Finding] func(F) string
// specific label only when provided with a finding.
type LazyLabelGetters[F Finding] map[Label]Getter[F]

// GetLabels returns a slice of labels from the list of lazy getters.
// GetLabels returns a slice of label names from the list of lazy getters.
func (ll LazyLabelGetters[F]) GetLabels() []string {
result := make([]string, 0, len(ll))
for _, label := range slices.Sorted(maps.Keys(ll)) {
for _, label := range ll.Labels() {
result = append(result, string(label))
}
return result
}

// Labels returns a sorted slice of labels from the list of lazy getters.
func (ll LazyLabelGetters[F]) Labels() []Label {
return slices.Sorted(maps.Keys(ll))
}

type Tracker interface {
// Gather the data and update the metrics registry.
Gather(context.Context)
Expand All @@ -59,6 +64,13 @@ type Tracker interface {
Reconfigure(*Configuration)
}

type Registration = struct {
Tracker
// GetGroupConfig returns the storage configuration associated to the
// tracker.
GetGroupConfig func(*storage.PrometheusMetrics) *storage.PrometheusMetrics_Group
}

// FindingErrorSequence is a sequence of pairs of findings and errors.
type FindingErrorSequence[F Finding] = iter.Seq2[F, error]

Expand Down Expand Up @@ -120,6 +132,11 @@ type TrackerBase[F Finding] struct {
cleanupWG sync.WaitGroup // for sync in testing.

registryFactory func(userID string) (metrics.CustomRegistry, error) // for mocking in tests.

// KnownLabels returns the list of labels accepted by this tracker for the
// given descriptors. Default implementation returns the getters map keys.
// Override to support dynamic labels (e.g., driven by the configuration).
KnownLabels func(map[string]*storage.PrometheusMetrics_Group_Labels) []Label
}

// MakeTrackerBase initializes a scoped tracker without any period or metrics
Expand Down Expand Up @@ -155,6 +172,9 @@ func makeTrackerBase[F Finding](metricPrefix, description string, scoped bool,
generator: generator,
scoped: scoped,
registryFactory: registryFactory,
KnownLabels: func(map[string]*storage.PrometheusMetrics_Group_Labels) []Label {
return getters.Labels()
},
}
}

Expand All @@ -165,7 +185,8 @@ func (tracker *TrackerBase[F]) NewConfiguration(cfg *storage.PrometheusMetrics_G
current = &Configuration{}
}

md, incFilters, excFilters, err := tracker.translateStorageConfiguration(cfg.GetDescriptors())
descriptors := cfg.GetDescriptors()
md, incFilters, excFilters, err := translateStorageConfiguration(descriptors, tracker.metricPrefix, tracker.KnownLabels(descriptors))
if err != nil {
return nil, err
}
Expand Down
33 changes: 16 additions & 17 deletions central/metrics/custom/tracker/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ func validateMetricName(name string) error {
return nil
}

// validateLabels checks if the labels exist in the labelOrder map and returns
// validateLabels checks if the labels exist in the known labels and returns
// a sorted label list.
func (tracker *TrackerBase[F]) validateLabels(labels []string, metricName string) ([]Label, error) {
func validateLabels(labels []string, metricName string, knownLabels []Label) ([]Label, error) {
if len(labels) == 0 {
return nil, errInvalidConfiguration.CausedByf("no labels specified for metric %q", metricName)
}
metricLabels := make([]Label, 0, len(labels))
for _, label := range labels {
validated, err := tracker.validateLabel(label, metricName)
validated, err := validateLabel(label, metricName, knownLabels)
if err != nil {
return nil, err
}
Expand All @@ -46,22 +46,22 @@ func (tracker *TrackerBase[F]) validateLabels(labels []string, metricName string
return metricLabels, nil
}

func (tracker *TrackerBase[F]) validateLabel(label string, metricName string) (Label, error) {
if _, ok := tracker.getters[Label(label)]; !ok {
return "", errInvalidConfiguration.CausedByf("label %q for metric %q is not in the list of known labels %v", label,
metricName, tracker.getters.GetLabels())
func validateLabel(label string, metricName string, knownLabels []Label) (Label, error) {
if slices.Contains(knownLabels, Label(label)) {
return Label(label), nil
}
return Label(label), nil
return "", errInvalidConfiguration.CausedByf("label %q for metric %q is not in the list of known labels %v", label,
metricName, knownLabels)
}

// parseFilters parses a map of label names to regex patterns, validating each label and pattern.
func (tracker *TrackerBase[F]) parseFilters(filters map[string]string, metricName, filterType string) (map[Label]*regexp.Regexp, error) {
func parseFilters(filters map[string]string, metricName, filterType string, knownLabels []Label) (map[Label]*regexp.Regexp, error) {
if len(filters) == 0 {
return nil, nil
}
patterns := make(map[Label]*regexp.Regexp, len(filters))
for label, pattern := range filters {
validated, err := tracker.validateLabel(label, metricName)
validated, err := validateLabel(label, metricName, knownLabels)
if err != nil {
return nil, err
}
Expand All @@ -83,34 +83,33 @@ func (tracker *TrackerBase[F]) parseFilters(filters map[string]string, metricNam

// translateStorageConfiguration converts the storage object to the usable map,
// validating the values.
func (tracker *TrackerBase[F]) translateStorageConfiguration(config map[string]*storage.PrometheusMetrics_Group_Labels) (MetricDescriptors, LabelFilters, LabelFilters, error) {
func translateStorageConfiguration(config map[string]*storage.PrometheusMetrics_Group_Labels, metricPrefix string, knownLabels []Label) (MetricDescriptors, LabelFilters, LabelFilters, error) {
result := make(MetricDescriptors, len(config))
metricPrefix := tracker.metricPrefix
if metricPrefix != "" {
metricPrefix += "_"
}
includeFilters := make(LabelFilters)
excludeFilters := make(LabelFilters)
for metricName, labels := range config {
metricName = metricPrefix + metricName
for descriptorKey, labels := range config {
metricName := metricPrefix + descriptorKey
if err := validateMetricName(metricName); err != nil {
return nil, nil, nil, errInvalidConfiguration.CausedByf(
"invalid metric name %q: %v", metricName, err)
}
metricLabels, err := tracker.validateLabels(labels.GetLabels(), metricName)
metricLabels, err := validateLabels(labels.GetLabels(), metricName, knownLabels)
if err != nil {
return nil, nil, nil, err
}

incPatterns, err := tracker.parseFilters(labels.GetIncludeFilters(), metricName, "include_filter")
incPatterns, err := parseFilters(labels.GetIncludeFilters(), metricName, "include_filter", knownLabels)
if err != nil {
return nil, nil, nil, err
}
if len(incPatterns) > 0 {
includeFilters[MetricName(metricName)] = incPatterns
}

excPatterns, err := tracker.parseFilters(labels.GetExcludeFilters(), metricName, "exclude_filter")
excPatterns, err := parseFilters(labels.GetExcludeFilters(), metricName, "exclude_filter", knownLabels)
if err != nil {
return nil, nil, nil, err
}
Expand Down
22 changes: 10 additions & 12 deletions central/metrics/custom/tracker/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ func TestTranslateConfiguration(t *testing.T) {
config := makeTestMetricLabels(t)
testFilters := makeTestLabelFilters(t)

tracker := MakeTrackerBase("test", "desc", testLabelGetters, nil)
md, incFilters, excFilters, err := tracker.translateStorageConfiguration(config)
md, incFilters, excFilters, err := translateStorageConfiguration(config, "test", testLabelGetters.Labels())
assert.NoError(t, err)
assert.Equal(t, makeTestMetricDescriptors(t), md)
assert.Empty(t, excFilters)
Expand Down Expand Up @@ -56,37 +55,38 @@ func Test_validateMetricName(t *testing.T) {
}

func Test_noLabels(t *testing.T) {
tracker := MakeTrackerBase("test", "desc", testLabelGetters, nil)
knownLabels := testLabelGetters.Labels()

for _, labels := range []*storage.PrometheusMetrics_Group_Labels{{Labels: []string{}}, {}, nil} {
config := map[string]*storage.PrometheusMetrics_Group_Labels{
"metric": labels,
}
md, _, _, err := tracker.translateStorageConfiguration(config)
md, _, _, err := translateStorageConfiguration(config, "test", knownLabels)
assert.Equal(t, `invalid configuration: no labels specified for metric "test_metric"`, err.Error())
assert.Empty(t, md)
}

md, _, _, err := tracker.translateStorageConfiguration(nil)
md, _, _, err := translateStorageConfiguration(nil, "test", knownLabels)
assert.NoError(t, err)
assert.Empty(t, md)
}

func Test_parseErrors(t *testing.T) {
knownLabels := testLabelGetters.Labels()

config := map[string]*storage.PrometheusMetrics_Group_Labels{
"metric1": {
Labels: []string{"unknown"},
},
}
tracker := MakeTrackerBase("test", "desc", testLabelGetters, nil)

md, _, _, err := tracker.translateStorageConfiguration(config)
md, _, _, err := translateStorageConfiguration(config, "test", knownLabels)
assert.Equal(t, `invalid configuration: label "unknown" for metric "test_metric1" is not in the list of known labels [CVE CVSS Cluster IsFixable Namespace Severity test]`, err.Error())
assert.Empty(t, md)

delete(config, "metric1")
config["met rick"] = nil
md, _, _, err = tracker.translateStorageConfiguration(config)
md, _, _, err = translateStorageConfiguration(config, "test", knownLabels)
assert.Equal(t, `invalid configuration: invalid metric name "test_met rick": doesn't match "^[a-zA-Z_:][a-zA-Z0-9_:]*$"`, err.Error())
assert.Empty(t, md)

Expand All @@ -98,9 +98,8 @@ func Test_parseErrors(t *testing.T) {
},
},
}
tracker = MakeTrackerBase("test", "desc", testLabelGetters, nil)

md, _, _, err = tracker.translateStorageConfiguration(config)
md, _, _, err = translateStorageConfiguration(config, "test", knownLabels)
assert.Equal(t, `invalid configuration: label "filter_unknown_label" for metric "test_metric1" is not in the list of known labels [CVE CVSS Cluster IsFixable Namespace Severity test]`, err.Error())
assert.Empty(t, md)

Expand All @@ -112,9 +111,8 @@ func Test_parseErrors(t *testing.T) {
},
},
}
tracker = MakeTrackerBase("test", "desc", testLabelGetters, nil)

md, _, _, err = tracker.translateStorageConfiguration(config)
md, _, _, err = translateStorageConfiguration(config, "test", knownLabels)
assert.Equal(t, "invalid configuration: bad include_filter expression for metric \"test_metric1\" label \"Namespace\": error parsing regexp: invalid character class range: `1-$`", err.Error())
assert.Empty(t, md)

Expand Down
5 changes: 3 additions & 2 deletions central/telemetry/centralclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/stackrox/rox/central/globaldb"
installationDS "github.com/stackrox/rox/central/installation/store"
"github.com/stackrox/rox/generated/storage"
"github.com/stackrox/rox/pkg/clientprofile"
"github.com/stackrox/rox/pkg/env"
"github.com/stackrox/rox/pkg/grpc/client/authn/basic"
"github.com/stackrox/rox/pkg/images/defaults"
Expand Down Expand Up @@ -41,7 +42,7 @@ type CentralClient struct {
*phonehome.Client

campaignMux sync.RWMutex
telemetryCampaign phonehome.APICallCampaign
telemetryCampaign clientprofile.RuleSet
}

// noopClient returns a disabled client.
Expand Down Expand Up @@ -221,7 +222,7 @@ func (c *CentralClient) Enable() {
go c.Track("Telemetry Enabled", nil)
}

func (c *CentralClient) appendRuntimeCampaign(campaign phonehome.APICallCampaign) {
func (c *CentralClient) appendRuntimeCampaign(campaign clientprofile.RuleSet) {
c.campaignMux.Lock()
defer c.campaignMux.Unlock()
c.telemetryCampaign = append(permanentTelemetryCampaign, campaign...)
Expand Down
Loading
Loading