Authn: Stat registration (#62934)

* reorganize auth usage stats

* usage stat privilege elevators

* stat count of modified role

* cfg related info

* add authn anon client

* kv store

* ensure anon enabled is collected even if client is not registered

* fix usage stats test
pull/62857/head
Jo 2 years ago committed by GitHub
parent ac942d5e72
commit 14a78b58e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      pkg/api/frontendsettings_test.go
  2. 3
      pkg/api/login_oauth_test.go
  3. 21
      pkg/infra/usagestats/statscollector/service.go
  4. 25
      pkg/infra/usagestats/statscollector/service_test.go
  5. 24
      pkg/login/social/social.go
  6. 7
      pkg/services/authn/authn.go
  7. 8
      pkg/services/authn/authnimpl/service.go
  8. 55
      pkg/services/authn/authnimpl/usage_stats.go
  9. 16
      pkg/services/authn/clients/anonymous.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()

@ -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(),
Features: featuremgmt.WithFeatures(),

@ -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)

@ -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"])

@ -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
}

@ -114,6 +114,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

@ -9,9 +9,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/login/social"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apikey"
@ -50,6 +52,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,
@ -67,6 +71,8 @@ func ProvideService(
postLoginHooks: newQueue[authn.PostLoginHookFn](),
}
usageStats.RegisterMetricsFunc(s.getUsageStats)
s.RegisterClient(clients.ProvideRender(userService, renderService))
s.RegisterClient(clients.ProvideAPIKey(apikeyService, userService))
@ -75,7 +81,7 @@ func ProvideService(
}
if s.cfg.AnonymousEnabled {
s.RegisterClient(clients.ProvideAnonymous(cfg, orgService))
s.RegisterClient(clients.ProvideAnonymous(cfg, orgService, kvstore))
}
var proxyClients []authn.ProxyClient

@ -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
}

@ -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
}

Loading…
Cancel
Save