mirror of
https://github.com/grafana/grafana.git
synced 2024-11-24 09:50:29 -06:00
Auth: Session cache [main] (#59935)
* Auth: Session cache [v9.2.x] (#59907) * add cache wrapper only cache token if not to rotate Co-authored-by: Kalle Persson <kalle.persson@grafana.com> anticipate next rotation Co-authored-by: ievaVasiljeva <ieva.vasiljeva@grafana.com> Co-authored-by: Kalle Persson <kalle.persson@grafana.com> Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com> (cherry picked from commit07a4b2343d
) * FeatureToggle: for storing sessions in a Remote Cache Co-authored-by: ievaVasiljeva <ieva.vasiljeva@grafana.com> (cherry picked from commitb8a8c15148
) * use feature flag for session cache * ensure ttl is minimum 1 second Co-authored-by: ievaVasiljeva <ieva.vasiljeva@grafana.com> Co-authored-by: Kalle Persson <kalle.persson@grafana.com> Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com> * ensure 2 ttl window to prevent caching of tokens near rotation Co-authored-by: Kalle Persson <kalle.persson@grafana.com> * fix description of toggle Co-authored-by: gamab <gabi.mabs@gmail.com> Co-authored-by: ievaVasiljeva <ieva.vasiljeva@grafana.com> Co-authored-by: Kalle Persson <kalle.persson@grafana.com> Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com> (cherry picked from commit2919588a82
) * fix broken quota test
This commit is contained in:
parent
13acddd605
commit
df4f0343e5
@ -91,6 +91,7 @@ Alpha features might be changed or removed without prior notice.
|
||||
| `elasticsearchBackendMigration` | Use Elasticsearch as backend data source |
|
||||
| `secureSocksDatasourceProxy` | Enable secure socks tunneling for supported core datasources |
|
||||
| `authnService` | Use new auth service to perform authentication |
|
||||
| `sessionRemoteCache` | Enable using remote cache for user sessions |
|
||||
|
||||
## Development feature toggles
|
||||
|
||||
|
@ -83,4 +83,5 @@ export interface FeatureToggles {
|
||||
elasticsearchBackendMigration?: boolean;
|
||||
secureSocksDatasourceProxy?: boolean;
|
||||
authnService?: boolean;
|
||||
sessionRemoteCache?: boolean;
|
||||
}
|
||||
|
@ -4,30 +4,43 @@ import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/remotecache"
|
||||
"github.com/grafana/grafana/pkg/infra/serverlock"
|
||||
"github.com/grafana/grafana/pkg/services/auth"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
const urgentRotateTime = 1 * time.Minute
|
||||
const (
|
||||
ttl = 15 * time.Second
|
||||
urgentRotateTime = 1 * time.Minute
|
||||
)
|
||||
|
||||
var getTime = time.Now
|
||||
|
||||
func ProvideUserAuthTokenService(sqlStore db.DB, cfg *setting.Cfg, serverLockService *serverlock.ServerLockService, quotaService quota.Service) (*UserAuthTokenService, error) {
|
||||
func ProvideUserAuthTokenService(sqlStore db.DB,
|
||||
serverLockService *serverlock.ServerLockService,
|
||||
remoteCache *remotecache.RemoteCache,
|
||||
features *featuremgmt.FeatureManager,
|
||||
quotaService quota.Service,
|
||||
cfg *setting.Cfg) (*UserAuthTokenService, error) {
|
||||
s := &UserAuthTokenService{
|
||||
sqlStore: sqlStore,
|
||||
serverLockService: serverLockService,
|
||||
cfg: cfg,
|
||||
log: log.New("auth"),
|
||||
remoteCache: remoteCache,
|
||||
features: features,
|
||||
}
|
||||
|
||||
defaultLimits, err := readQuotaConfig(cfg)
|
||||
@ -43,6 +56,8 @@ func ProvideUserAuthTokenService(sqlStore db.DB, cfg *setting.Cfg, serverLockSer
|
||||
return s, err
|
||||
}
|
||||
|
||||
remotecache.Register(auth.UserToken{})
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
@ -51,6 +66,8 @@ type UserAuthTokenService struct {
|
||||
serverLockService *serverlock.ServerLockService
|
||||
cfg *setting.Cfg
|
||||
log log.Logger
|
||||
remoteCache *remotecache.RemoteCache
|
||||
features *featuremgmt.FeatureManager
|
||||
}
|
||||
|
||||
func (s *UserAuthTokenService) CreateToken(ctx context.Context, user *user.User, clientIP net.IP, userAgent string) (*auth.UserToken, error) {
|
||||
@ -101,7 +118,52 @@ func (s *UserAuthTokenService) CreateToken(ctx context.Context, user *user.User,
|
||||
return &userToken, err
|
||||
}
|
||||
|
||||
func (s *UserAuthTokenService) lookupTokenWithCache(ctx context.Context, unhashedToken string) (*auth.UserToken, error) {
|
||||
hashedToken := hashToken(unhashedToken)
|
||||
cacheKey := "auth_token:" + hashedToken
|
||||
|
||||
session, errCache := s.remoteCache.Get(ctx, cacheKey)
|
||||
if errCache == nil {
|
||||
token := session.(auth.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) (*auth.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) (*auth.UserToken, error) {
|
||||
hashedToken := hashToken(unhashedToken)
|
||||
var model userAuthToken
|
||||
var exists bool
|
||||
|
@ -378,5 +378,10 @@ var (
|
||||
Description: "Use new auth service to perform authentication",
|
||||
State: FeatureStateAlpha,
|
||||
},
|
||||
{
|
||||
Name: "sessionRemoteCache",
|
||||
Description: "Enable using remote cache for user sessions",
|
||||
State: FeatureStateAlpha,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
@ -274,4 +274,8 @@ const (
|
||||
// FlagAuthnService
|
||||
// Use new auth service to perform authentication
|
||||
FlagAuthnService = "authnService"
|
||||
|
||||
// FlagSessionRemoteCache
|
||||
// Enable using remote cache for user sessions
|
||||
FlagSessionRemoteCache = "sessionRemoteCache"
|
||||
)
|
||||
|
@ -465,7 +465,7 @@ func getQuotaBySrvTargetScope(t *testing.T, quotaService quota.Service, srv quot
|
||||
func setupEnv(t *testing.T, sqlStore *sqlstore.SQLStore, b bus.Bus, quotaService quota.Service) {
|
||||
_, err := apikeyimpl.ProvideService(sqlStore, sqlStore.Cfg, quotaService)
|
||||
require.NoError(t, err)
|
||||
_, err = authimpl.ProvideUserAuthTokenService(sqlStore, sqlStore.Cfg, nil, quotaService)
|
||||
_, err = authimpl.ProvideUserAuthTokenService(sqlStore, nil, nil, featuremgmt.WithFeatures(), quotaService, sqlStore.Cfg)
|
||||
require.NoError(t, err)
|
||||
_, err = dashboardStore.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
|
Loading…
Reference in New Issue
Block a user