From 2919588a82d0a11c6400453d9b8ecf3b94e670bf Mon Sep 17 00:00:00 2001 From: Jo Date: Wed, 7 Dec 2022 09:04:05 +0000 Subject: [PATCH] Auth: Session cache [v9.2.x] (#59907) * add cache wrapper only cache token if not to rotate Co-authored-by: Kalle Persson anticipate next rotation Co-authored-by: ievaVasiljeva Co-authored-by: Kalle Persson Co-authored-by: Gabriel MABILLE (cherry picked from commit 07a4b2343d59cb593ad128355e1d9217ba493700) * FeatureToggle: for storing sessions in a Remote Cache Co-authored-by: ievaVasiljeva (cherry picked from commit b8a8c15148c53a76f14f76da1b0fa4cec9be9216) * use feature flag for session cache * ensure ttl is minimum 1 second Co-authored-by: ievaVasiljeva Co-authored-by: Kalle Persson Co-authored-by: Gabriel MABILLE * ensure 2 ttl window to prevent caching of tokens near rotation Co-authored-by: Kalle Persson * fix description of toggle Co-authored-by: gamab Co-authored-by: ievaVasiljeva Co-authored-by: Kalle Persson Co-authored-by: Gabriel MABILLE --- .../src/types/featureToggles.gen.ts | 1 + pkg/services/auth/auth_token.go | 65 ++++++++++++++++++- pkg/services/featuremgmt/registry.go | 5 ++ pkg/services/featuremgmt/toggles_gen.go | 4 ++ 4 files changed, 73 insertions(+), 2 deletions(-) diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 40e7df87ef4..dc42b165594 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -69,4 +69,5 @@ export interface FeatureToggles { increaseInMemDatabaseQueryCache?: boolean; userRemoteCache?: boolean; datasourceLogger?: boolean; + sessionRemoteCache?: boolean; } diff --git a/pkg/services/auth/auth_token.go b/pkg/services/auth/auth_token.go index d9c516482dc..fe0e1ca6cea 100644 --- a/pkg/services/auth/auth_token.go +++ b/pkg/services/auth/auth_token.go @@ -4,14 +4,17 @@ import ( "context" "crypto/sha256" "encoding/hex" + "errors" "net" "strings" "time" + "github.com/grafana/grafana/pkg/infra/remotecache" "github.com/grafana/grafana/pkg/infra/serverlock" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" @@ -22,16 +25,27 @@ const ServiceName = "UserAuthTokenService" var getTime = time.Now -const urgentRotateTime = 1 * time.Minute +const ( + ttl = 15 * time.Second + urgentRotateTime = 1 * time.Minute +) -func ProvideUserAuthTokenService(sqlStore *sqlstore.SQLStore, serverLockService *serverlock.ServerLockService, +func ProvideUserAuthTokenService(sqlStore *sqlstore.SQLStore, + serverLockService *serverlock.ServerLockService, + remoteCache *remotecache.RemoteCache, + features *featuremgmt.FeatureManager, cfg *setting.Cfg) *UserAuthTokenService { s := &UserAuthTokenService{ SQLStore: sqlStore, ServerLockService: serverLockService, Cfg: cfg, log: log.New("auth"), + remoteCache: remoteCache, + features: features, } + + remotecache.Register(models.UserToken{}) + return s } @@ -40,6 +54,8 @@ type UserAuthTokenService struct { ServerLockService *serverlock.ServerLockService Cfg *setting.Cfg log log.Logger + remoteCache *remotecache.RemoteCache + features *featuremgmt.FeatureManager } type ActiveAuthTokenService struct { @@ -118,7 +134,52 @@ func (s *UserAuthTokenService) CreateToken(ctx context.Context, user *user.User, return &userToken, err } +func (s *UserAuthTokenService) lookupTokenWithCache(ctx context.Context, unhashedToken string) (*models.UserToken, error) { + hashedToken := hashToken(unhashedToken) + cacheKey := "auth_token:" + hashedToken + + session, errCache := s.remoteCache.Get(ctx, cacheKey) + if errCache == nil { + token := session.(models.UserToken) + return &token, nil + } else { + if errors.Is(errCache, remotecache.ErrCacheItemNotFound) { + s.log.Debug("user auth token not found in cache", + "cacheKey", cacheKey) + } else { + s.log.Warn("failed to get user auth token from cache", + "cacheKey", cacheKey, "error", errCache) + } + } + + token, err := s.lookupToken(ctx, unhashedToken) + if err != nil { + return nil, err + } + + // only cache tokens until their near rotation time + // Near rotation time = tokens last rotation plus the rotation interval minus 2 ttl (=30s by default) + nextRotation := time.Unix(token.RotatedAt, 0). + Add(-2 * ttl). // subtract 2 ttl to make sure we don't cache tokens that are about to expire + Add(time.Duration(s.Cfg.TokenRotationIntervalMinutes) * time.Minute) + if now := getTime(); now.Before(nextRotation) { + if err := s.remoteCache.Set(ctx, cacheKey, *token, ttl); err != nil { + s.log.Warn("could not cache token", "error", err, "cacheKey", cacheKey, "userId", token.UserId) + } + } + + return token, nil +} + func (s *UserAuthTokenService) LookupToken(ctx context.Context, unhashedToken string) (*models.UserToken, error) { + if s.features != nil && s.features.IsEnabled(featuremgmt.FlagSessionRemoteCache) { + return s.lookupTokenWithCache(ctx, unhashedToken) + } + + return s.lookupToken(ctx, unhashedToken) +} + +func (s *UserAuthTokenService) lookupToken(ctx context.Context, unhashedToken string) (*models.UserToken, error) { hashedToken := hashToken(unhashedToken) var model userAuthToken var exists bool diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 76916f0b31b..62e9ba4d5c9 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -292,5 +292,10 @@ var ( Name: "datasourceLogger", Description: "Logs all datasource requests", }, + { + Name: "sessionRemoteCache", + Description: "Enable using remote cache for user sessions", + State: FeatureStateAlpha, + }, } ) diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 428f3e0030e..19733a3b9b2 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -218,4 +218,8 @@ const ( // FlagDatasourceLogger // Logs all datasource requests FlagDatasourceLogger = "datasourceLogger" + + // FlagSessionRemoteCache + // Enable using remote cache for user sessions + FlagSessionRemoteCache = "sessionRemoteCache" )