diff --git a/pkg/api/frontendsettings_test.go b/pkg/api/frontendsettings_test.go index 07d3a4d1ab5..6b44f8cda17 100644 --- a/pkg/api/frontendsettings_test.go +++ b/pkg/api/frontendsettings_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/infra/usagestats" "github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/config" @@ -64,7 +65,7 @@ func setupTestEnvironment(t *testing.T, cfg *setting.Cfg, features *featuremgmt. PluginsCDNURLTemplate: cfg.PluginsCDNURLTemplate, PluginSettings: cfg.PluginSettings, }), - SocialService: social.ProvideService(cfg, features), + SocialService: social.ProvideService(cfg, features, &usagestats.UsageStatsMock{}), } m := web.New() diff --git a/pkg/api/login_oauth_test.go b/pkg/api/login_oauth_test.go index b8b798e1deb..e2426e7902e 100644 --- a/pkg/api/login_oauth_test.go +++ b/pkg/api/login_oauth_test.go @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/infra/usagestats" "github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/models/roletype" "github.com/grafana/grafana/pkg/services/featuremgmt" @@ -31,7 +32,7 @@ func setupSocialHTTPServerWithConfig(t *testing.T, cfg *setting.Cfg) *HTTPServer Cfg: cfg, License: &licensing.OSSLicensingService{Cfg: cfg}, SQLStore: sqlStore, - SocialService: social.ProvideService(cfg, featuremgmt.WithFeatures()), + SocialService: social.ProvideService(cfg, featuremgmt.WithFeatures(), &usagestats.UsageStatsMock{}), HooksService: hooks.ProvideService(), SecretsService: fakes.NewFakeSecretsService(), } diff --git a/pkg/infra/usagestats/statscollector/service.go b/pkg/infra/usagestats/statscollector/service.go index a2b814ac6b1..5276568e1bc 100644 --- a/pkg/infra/usagestats/statscollector/service.go +++ b/pkg/infra/usagestats/statscollector/service.go @@ -26,7 +26,6 @@ type Service struct { cfg *setting.Cfg sqlstore db.DB plugins plugins.Store - social social.Service usageStats usagestats.Service statsService stats.Service features *featuremgmt.FeatureManager @@ -56,7 +55,6 @@ func ProvideService( cfg: cfg, sqlstore: store, plugins: plugins, - social: social, usageStats: us, statsService: statsService, features: features, @@ -178,25 +176,6 @@ func (s *Service) collectSystemStats(ctx context.Context) (map[string]interface{ m["stats.packaging."+s.cfg.Packaging+".count"] = 1 m["stats.distributor."+s.cfg.ReportingDistributor+".count"] = 1 - // Add stats about auth configuration - authTypes := map[string]bool{} - authTypes["anonymous"] = s.cfg.AnonymousEnabled - authTypes["basic_auth"] = s.cfg.BasicAuthEnabled - authTypes["ldap"] = s.cfg.LDAPEnabled - authTypes["auth_proxy"] = s.cfg.AuthProxyEnabled - - for provider, enabled := range s.social.GetOAuthProviders() { - authTypes["oauth_"+provider] = enabled - } - - for authType, enabled := range authTypes { - enabledValue := 0 - if enabled { - enabledValue = 1 - } - m["stats.auth_enabled."+authType+".count"] = enabledValue - } - m["stats.uptime"] = int64(time.Since(s.startTime).Seconds()) featureUsageStats := s.features.GetUsageStats(ctx) diff --git a/pkg/infra/usagestats/statscollector/service_test.go b/pkg/infra/usagestats/statscollector/service_test.go index cb8074dedb1..0219e6b264e 100644 --- a/pkg/infra/usagestats/statscollector/service_test.go +++ b/pkg/infra/usagestats/statscollector/service_test.go @@ -7,10 +7,11 @@ import ( "testing" "time" - sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" + "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/db/dbtest" @@ -160,17 +161,6 @@ func TestCollectingUsageStats(t *testing.T) { createConcurrentTokens(t, sqlStore) - s.social = &mockSocial{ - OAuthProviders: map[string]bool{ - "github": true, - "gitlab": true, - "azuread": true, - "google": true, - "generic_oauth": true, - "grafana_com": true, - }, - } - metrics, err := s.collectSystemStats(context.Background()) require.NoError(t, err) @@ -183,17 +173,6 @@ func TestCollectingUsageStats(t *testing.T) { assert.EqualValues(t, 19, metrics["stats.library_panels.count"]) assert.EqualValues(t, 20, metrics["stats.library_variables.count"]) - assert.EqualValues(t, 1, metrics["stats.auth_enabled.anonymous.count"]) - assert.EqualValues(t, 1, metrics["stats.auth_enabled.basic_auth.count"]) - assert.EqualValues(t, 1, metrics["stats.auth_enabled.ldap.count"]) - assert.EqualValues(t, 1, metrics["stats.auth_enabled.auth_proxy.count"]) - assert.EqualValues(t, 1, metrics["stats.auth_enabled.oauth_github.count"]) - assert.EqualValues(t, 1, metrics["stats.auth_enabled.oauth_gitlab.count"]) - assert.EqualValues(t, 1, metrics["stats.auth_enabled.oauth_google.count"]) - assert.EqualValues(t, 1, metrics["stats.auth_enabled.oauth_azuread.count"]) - assert.EqualValues(t, 1, metrics["stats.auth_enabled.oauth_generic_oauth.count"]) - assert.EqualValues(t, 1, metrics["stats.auth_enabled.oauth_grafana_com.count"]) - assert.EqualValues(t, 1, metrics["stats.packaging.deb.count"]) assert.EqualValues(t, 1, metrics["stats.distributor.hosted-grafana.count"]) diff --git a/pkg/login/social/social.go b/pkg/login/social/social.go index 5ff006a5827..e1fde5d4916 100644 --- a/pkg/login/social/social.go +++ b/pkg/login/social/social.go @@ -15,6 +15,7 @@ import ( "golang.org/x/text/language" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/usagestats" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/setting" @@ -61,6 +62,7 @@ type OAuthInfo struct { func ProvideService(cfg *setting.Cfg, features *featuremgmt.FeatureManager, + usageStats usagestats.Service, ) *SocialService { ss := SocialService{ cfg: cfg, @@ -68,6 +70,8 @@ func ProvideService(cfg *setting.Cfg, socialMap: make(map[string]SocialConnector), } + usageStats.RegisterMetricsFunc(ss.getUsageStats) + for _, name := range allOauthes { sec := cfg.Raw.Section("auth." + name) @@ -461,3 +465,23 @@ func (ss *SocialService) GetOAuthInfoProvider(name string) *OAuthInfo { func (ss *SocialService) GetOAuthInfoProviders() map[string]*OAuthInfo { return ss.oAuthProvider } + +func (ss *SocialService) getUsageStats(ctx context.Context) (map[string]interface{}, error) { + m := map[string]interface{}{} + + authTypes := map[string]bool{} + for provider, enabled := range ss.GetOAuthProviders() { + authTypes["oauth_"+provider] = enabled + } + + for authType, enabled := range authTypes { + enabledValue := 0 + if enabled { + enabledValue = 1 + } + + m["stats.auth_enabled."+authType+".count"] = enabledValue + } + + return m, nil +} diff --git a/pkg/services/authn/authn.go b/pkg/services/authn/authn.go index 64522ff26b5..ec0467744b3 100644 --- a/pkg/services/authn/authn.go +++ b/pkg/services/authn/authn.go @@ -94,6 +94,13 @@ type ProxyClient interface { AuthenticateProxy(ctx context.Context, r *Request, username string, additional map[string]string) (*Identity, error) } +// UsageStatClient is an optional interface that auth clients can implement. +// Clients that implements this interface can specify a usage stat collection hook +type UsageStatClient interface { + Client + UsageStatFn(ctx context.Context) (map[string]interface{}, error) +} + type Request struct { // OrgID will be populated by authn.Service OrgID int64 diff --git a/pkg/services/authn/authnimpl/service.go b/pkg/services/authn/authnimpl/service.go index cba480ee297..8c83cff7e24 100644 --- a/pkg/services/authn/authnimpl/service.go +++ b/pkg/services/authn/authnimpl/service.go @@ -8,9 +8,11 @@ import ( "github.com/hashicorp/go-multierror" "go.opentelemetry.io/otel/attribute" + "github.com/grafana/grafana/pkg/infra/kvstore" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/network" "github.com/grafana/grafana/pkg/infra/tracing" + "github.com/grafana/grafana/pkg/infra/usagestats" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/apikey" "github.com/grafana/grafana/pkg/services/auth" @@ -48,6 +50,8 @@ func ProvideService( accessControlService accesscontrol.Service, apikeyService apikey.Service, userService user.Service, jwtService auth.JWTVerifierService, + usageStats usagestats.Service, + kvstore kvstore.KVStore, userProtectionService login.UserProtectionService, loginAttempts loginattempt.Service, quotaService quota.Service, authInfoService login.AuthInfoService, renderService rendering.Service, @@ -64,6 +68,8 @@ func ProvideService( postLoginHooks: newQueue[authn.PostLoginHookFn](), } + usageStats.RegisterMetricsFunc(s.getUsageStats) + s.RegisterClient(clients.ProvideRender(userService, renderService)) s.RegisterClient(clients.ProvideAPIKey(apikeyService, userService)) @@ -74,7 +80,7 @@ func ProvideService( } if s.cfg.AnonymousEnabled { - s.RegisterClient(clients.ProvideAnonymous(cfg, orgService)) + s.RegisterClient(clients.ProvideAnonymous(cfg, orgService, kvstore)) } var proxyClients []authn.ProxyClient diff --git a/pkg/services/authn/authnimpl/usage_stats.go b/pkg/services/authn/authnimpl/usage_stats.go new file mode 100644 index 00000000000..1dd9f1d36c0 --- /dev/null +++ b/pkg/services/authn/authnimpl/usage_stats.go @@ -0,0 +1,55 @@ +package authnimpl + +import ( + "context" + + "github.com/grafana/grafana/pkg/services/authn" + "github.com/grafana/grafana/pkg/setting" +) + +func (s *Service) getUsageStats(ctx context.Context) (map[string]interface{}, error) { + m := map[string]interface{}{} + + // Add stats about auth configuration + authTypes := map[string]bool{} + authTypes["basic_auth"] = s.cfg.BasicAuthEnabled + authTypes["ldap"] = s.cfg.LDAPEnabled + authTypes["auth_proxy"] = s.cfg.AuthProxyEnabled + authTypes["anonymous"] = s.cfg.AnonymousEnabled + + for authType, enabled := range authTypes { + enabledValue := 0 + if enabled { + enabledValue = 1 + } + m["stats.auth_enabled."+authType+".count"] = enabledValue + } + + // Add stats about privilege elevators. + // FIXME: Move this to accesscontrol OSS. + // FIXME: Access Control OSS usage stats is currently disabled if Enterprise is enabled. + m["stats.authz.viewers_can_edit.count"] = 0 + if setting.ViewersCanEdit { + m["stats.authz.viewers_can_edit.count"] = 1 + } + + m["stats.authz.editors_can_admin.count"] = 0 + if s.cfg.EditorsCanAdmin { + m["stats.authz.editors_can_admin.count"] = 1 + } + + for _, client := range s.clients { + if usac, ok := client.(authn.UsageStatClient); ok { + clientStats, err := usac.UsageStatFn(ctx) + if err != nil { + s.log.Warn("Failed to get usage stats from client", "client", client.Name(), "error", err) + } + + for k, v := range clientStats { + m[k] = v + } + } + } + + return m, nil +} diff --git a/pkg/services/authn/clients/anonymous.go b/pkg/services/authn/clients/anonymous.go index e37b0ebb394..41ec764567d 100644 --- a/pkg/services/authn/clients/anonymous.go +++ b/pkg/services/authn/clients/anonymous.go @@ -2,7 +2,9 @@ package clients import ( "context" + "strings" + "github.com/grafana/grafana/pkg/infra/kvstore" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/org" @@ -11,7 +13,7 @@ import ( var _ authn.ContextAwareClient = new(Anonymous) -func ProvideAnonymous(cfg *setting.Cfg, orgService org.Service) *Anonymous { +func ProvideAnonymous(cfg *setting.Cfg, orgService org.Service, _ kvstore.KVStore) *Anonymous { return &Anonymous{ cfg: cfg, log: log.New("authn.anonymous"), @@ -53,3 +55,15 @@ func (a *Anonymous) Test(ctx context.Context, r *authn.Request) bool { func (a *Anonymous) Priority() uint { return 100 } + +func (a *Anonymous) UsageStatFn(ctx context.Context) (map[string]interface{}, error) { + m := map[string]interface{}{} + + // Add stats about anonymous auth + m["stats.anonymous.customized_role.count"] = 0 + if !strings.EqualFold(a.cfg.AnonymousOrgRole, "Viewer") { + m["stats.anonymous.customized_role.count"] = 1 + } + + return m, nil +}