AuthN: add metrics for login and authentication (#63783)

* AuthN: Add metrics
This commit is contained in:
Karl Persson 2023-03-06 17:07:57 +01:00 committed by GitHub
parent b6eb324139
commit 66ef5325d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 76 additions and 2 deletions

View File

@ -0,0 +1,58 @@
package authnimpl
import (
"github.com/prometheus/client_golang/prometheus"
)
const (
metricsSubSystem = "authn"
metricsNamespace = "grafana"
)
type metrics struct {
failedAuth prometheus.Counter
successfulAuth *prometheus.CounterVec
failedLogin *prometheus.CounterVec
successfulLogin *prometheus.CounterVec
}
func newMetrics(reg prometheus.Registerer) *metrics {
m := &metrics{
failedAuth: prometheus.NewCounter(prometheus.CounterOpts{
Namespace: metricsNamespace,
Subsystem: metricsSubSystem,
Name: "authn_failed_authentication_total",
Help: "Number of failed authentications",
}),
successfulAuth: prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: metricsNamespace,
Subsystem: metricsSubSystem,
Name: "authn_successful_authentication_total",
Help: "Number of successful authentications",
}, []string{"client"}),
failedLogin: prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: metricsNamespace,
Subsystem: metricsSubSystem,
Name: "authn_failed_login_total",
Help: "Number of failed logins",
}, []string{"client"}),
successfulLogin: prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: metricsNamespace,
Subsystem: metricsSubSystem,
Name: "authn_successful_login_total",
Help: "Number of successful logins",
}, []string{"client"}),
}
if reg != nil {
reg.MustRegister(
m.failedAuth,
m.successfulAuth,
m.failedLogin,
m.successfulLogin,
)
}
return m
}

View File

@ -6,6 +6,7 @@ import (
"strconv"
"github.com/hashicorp/go-multierror"
"github.com/prometheus/client_golang/prometheus"
"go.opentelemetry.io/otel/attribute"
"github.com/grafana/grafana/pkg/infra/log"
@ -60,7 +61,7 @@ func ProvideService(
authInfoService login.AuthInfoService, renderService rendering.Service,
features *featuremgmt.FeatureManager, oauthTokenService oauthtoken.OAuthTokenService,
socialService social.Service, cache *remotecache.RemoteCache,
ldapService service.LDAP,
ldapService service.LDAP, registerer prometheus.Registerer,
) *Service {
s := &Service{
log: log.New("authn.service"),
@ -68,6 +69,7 @@ func ProvideService(
clients: make(map[string]authn.Client),
clientQueue: newQueue[authn.ContextAwareClient](),
tracer: tracer,
metrics: newMetrics(registerer),
sessionService: sessionService,
postAuthHooks: newQueue[authn.PostAuthHookFn](),
postLoginHooks: newQueue[authn.PostLoginHookFn](),
@ -164,7 +166,9 @@ type Service struct {
clients map[string]authn.Client
clientQueue *queue[authn.ContextAwareClient]
tracer tracing.Tracer
tracer tracing.Tracer
metrics *metrics
sessionService auth.UserTokenService
// postAuthHooks are called after a successful authentication. They can modify the identity.
@ -188,12 +192,14 @@ func (s *Service) Authenticate(ctx context.Context, r *authn.Request) (*authn.Id
}
if identity != nil {
s.metrics.successfulAuth.WithLabelValues(item.v.Name()).Inc()
return identity, nil
}
}
}
if authErr != nil {
s.metrics.failedAuth.Inc()
return nil, authErr
}
@ -234,6 +240,10 @@ func (s *Service) RegisterPostAuthHook(hook authn.PostAuthHookFn, priority uint)
}
func (s *Service) Login(ctx context.Context, client string, r *authn.Request) (identity *authn.Identity, err error) {
ctx, span := s.tracer.Start(ctx, "authn.Login")
defer span.End()
span.SetAttributes(attributeKeyClient, client, attribute.Key(attributeKeyClient).String(client))
defer func() {
for _, hook := range s.postLoginHooks.items {
hook.v(ctx, identity, r, err)
@ -242,11 +252,13 @@ func (s *Service) Login(ctx context.Context, client string, r *authn.Request) (i
c, ok := s.clients[client]
if !ok {
s.metrics.failedLogin.WithLabelValues(client).Inc()
return nil, authn.ErrClientNotConfigured.Errorf("client not configured: %s", client)
}
identity, err = s.authenticate(ctx, c, r)
if err != nil {
s.metrics.failedLogin.WithLabelValues(client).Inc()
return nil, err
}
@ -254,6 +266,7 @@ func (s *Service) Login(ctx context.Context, client string, r *authn.Request) (i
// Login is only supported for users
if namespace != authn.NamespaceUser || id <= 0 {
s.metrics.failedLogin.WithLabelValues(client).Inc()
return nil, authn.ErrUnsupportedIdentity.Errorf("expected identity of type user but got: %s", namespace)
}
@ -265,10 +278,12 @@ func (s *Service) Login(ctx context.Context, client string, r *authn.Request) (i
sessionToken, err := s.sessionService.CreateToken(ctx, &user.User{ID: id}, ip, r.HTTPRequest.UserAgent())
if err != nil {
s.metrics.failedLogin.WithLabelValues(client).Inc()
s.log.FromContext(ctx).Error("Failed to create session", "client", client, "id", identity.ID, "err", err)
return nil, err
}
s.metrics.successfulLogin.WithLabelValues(client).Inc()
identity.SessionToken = sessionToken
return identity, nil
}

View File

@ -316,6 +316,7 @@ func setupTests(t *testing.T, opts ...func(svc *Service)) *Service {
clients: map[string]authn.Client{},
clientQueue: newQueue[authn.ContextAwareClient](),
tracer: tracing.InitializeTracerForTest(),
metrics: newMetrics(nil),
postAuthHooks: newQueue[authn.PostAuthHookFn](),
postLoginHooks: newQueue[authn.PostLoginHookFn](),
}