|
|
@ -2,7 +2,9 @@ package idimpl |
|
|
|
|
|
|
|
|
|
|
|
import ( |
|
|
|
import ( |
|
|
|
"context" |
|
|
|
"context" |
|
|
|
|
|
|
|
"errors" |
|
|
|
"fmt" |
|
|
|
"fmt" |
|
|
|
|
|
|
|
"strconv" |
|
|
|
"time" |
|
|
|
"time" |
|
|
|
|
|
|
|
|
|
|
|
"github.com/go-jose/go-jose/v3/jwt" |
|
|
|
"github.com/go-jose/go-jose/v3/jwt" |
|
|
@ -15,6 +17,8 @@ import ( |
|
|
|
"github.com/grafana/grafana/pkg/services/auth/identity" |
|
|
|
"github.com/grafana/grafana/pkg/services/auth/identity" |
|
|
|
"github.com/grafana/grafana/pkg/services/authn" |
|
|
|
"github.com/grafana/grafana/pkg/services/authn" |
|
|
|
"github.com/grafana/grafana/pkg/services/featuremgmt" |
|
|
|
"github.com/grafana/grafana/pkg/services/featuremgmt" |
|
|
|
|
|
|
|
"github.com/grafana/grafana/pkg/services/login" |
|
|
|
|
|
|
|
"github.com/grafana/grafana/pkg/services/user" |
|
|
|
"github.com/grafana/grafana/pkg/setting" |
|
|
|
"github.com/grafana/grafana/pkg/setting" |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
@ -28,9 +32,14 @@ var _ auth.IDService = (*Service)(nil) |
|
|
|
|
|
|
|
|
|
|
|
func ProvideService( |
|
|
|
func ProvideService( |
|
|
|
cfg *setting.Cfg, signer auth.IDSigner, cache remotecache.CacheStorage, |
|
|
|
cfg *setting.Cfg, signer auth.IDSigner, cache remotecache.CacheStorage, |
|
|
|
features featuremgmt.FeatureToggles, authnService authn.Service, reg prometheus.Registerer, |
|
|
|
features featuremgmt.FeatureToggles, authnService authn.Service, |
|
|
|
|
|
|
|
authInfoService login.AuthInfoService, reg prometheus.Registerer, |
|
|
|
) *Service { |
|
|
|
) *Service { |
|
|
|
s := &Service{cfg: cfg, logger: log.New("id-service"), signer: signer, cache: cache, metrics: newMetrics(reg)} |
|
|
|
s := &Service{ |
|
|
|
|
|
|
|
cfg: cfg, logger: log.New("id-service"), |
|
|
|
|
|
|
|
signer: signer, cache: cache, |
|
|
|
|
|
|
|
authInfoService: authInfoService, metrics: newMetrics(reg), |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if features.IsEnabledGlobally(featuremgmt.FlagIdForwarding) { |
|
|
|
if features.IsEnabledGlobally(featuremgmt.FlagIdForwarding) { |
|
|
|
authnService.RegisterPostAuthHook(s.hook, 140) |
|
|
|
authnService.RegisterPostAuthHook(s.hook, 140) |
|
|
@ -40,12 +49,13 @@ func ProvideService( |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
type Service struct { |
|
|
|
type Service struct { |
|
|
|
cfg *setting.Cfg |
|
|
|
cfg *setting.Cfg |
|
|
|
logger log.Logger |
|
|
|
logger log.Logger |
|
|
|
signer auth.IDSigner |
|
|
|
signer auth.IDSigner |
|
|
|
cache remotecache.CacheStorage |
|
|
|
cache remotecache.CacheStorage |
|
|
|
si singleflight.Group |
|
|
|
authInfoService login.AuthInfoService |
|
|
|
metrics *metrics |
|
|
|
si singleflight.Group |
|
|
|
|
|
|
|
metrics *metrics |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (s *Service) SignIdentity(ctx context.Context, id identity.Requester) (string, error) { |
|
|
|
func (s *Service) SignIdentity(ctx context.Context, id identity.Requester) (string, error) { |
|
|
@ -61,15 +71,15 @@ func (s *Service) SignIdentity(ctx context.Context, id identity.Requester) (stri |
|
|
|
cachedToken, err := s.cache.Get(ctx, cacheKey) |
|
|
|
cachedToken, err := s.cache.Get(ctx, cacheKey) |
|
|
|
if err == nil { |
|
|
|
if err == nil { |
|
|
|
s.metrics.tokenSigningFromCacheCounter.Inc() |
|
|
|
s.metrics.tokenSigningFromCacheCounter.Inc() |
|
|
|
s.logger.Debug("Cached token found", "namespace", namespace, "id", identifier) |
|
|
|
s.logger.FromContext(ctx).Debug("Cached token found", "namespace", namespace, "id", identifier) |
|
|
|
return string(cachedToken), nil |
|
|
|
return string(cachedToken), nil |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
s.metrics.tokenSigningCounter.Inc() |
|
|
|
s.metrics.tokenSigningCounter.Inc() |
|
|
|
s.logger.Debug("Sign new id token", "namespace", namespace, "id", identifier) |
|
|
|
s.logger.FromContext(ctx).Debug("Sign new id token", "namespace", namespace, "id", identifier) |
|
|
|
|
|
|
|
|
|
|
|
now := time.Now() |
|
|
|
now := time.Now() |
|
|
|
token, err := s.signer.SignIDToken(ctx, &auth.IDClaims{ |
|
|
|
claims := &auth.IDClaims{ |
|
|
|
Claims: jwt.Claims{ |
|
|
|
Claims: jwt.Claims{ |
|
|
|
Issuer: s.cfg.AppURL, |
|
|
|
Issuer: s.cfg.AppURL, |
|
|
|
Audience: getAudience(id.GetOrgID()), |
|
|
|
Audience: getAudience(id.GetOrgID()), |
|
|
@ -77,15 +87,22 @@ func (s *Service) SignIdentity(ctx context.Context, id identity.Requester) (stri |
|
|
|
Expiry: jwt.NewNumericDate(now.Add(tokenTTL)), |
|
|
|
Expiry: jwt.NewNumericDate(now.Add(tokenTTL)), |
|
|
|
IssuedAt: jwt.NewNumericDate(now), |
|
|
|
IssuedAt: jwt.NewNumericDate(now), |
|
|
|
}, |
|
|
|
}, |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if identity.IsNamespace(namespace, identity.NamespaceUser) { |
|
|
|
|
|
|
|
if err := s.setUserClaims(ctx, identifier, claims); err != nil { |
|
|
|
|
|
|
|
return "", err |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
token, err := s.signer.SignIDToken(ctx, claims) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
s.metrics.failedTokenSigningCounter.Inc() |
|
|
|
s.metrics.failedTokenSigningCounter.Inc() |
|
|
|
return "", err |
|
|
|
return "", err |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if err := s.cache.Set(ctx, cacheKey, []byte(token), cacheTTL); err != nil { |
|
|
|
if err := s.cache.Set(ctx, cacheKey, []byte(token), cacheTTL); err != nil { |
|
|
|
s.logger.Error("Failed to add id token to cache", "error", err) |
|
|
|
s.logger.FromContext(ctx).Error("Failed to add id token to cache", "error", err) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return token, nil |
|
|
|
return token, nil |
|
|
@ -98,12 +115,37 @@ func (s *Service) SignIdentity(ctx context.Context, id identity.Requester) (stri |
|
|
|
return result.(string), nil |
|
|
|
return result.(string), nil |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (s *Service) setUserClaims(ctx context.Context, identifier string, claims *auth.IDClaims) error { |
|
|
|
|
|
|
|
id, err := strconv.ParseInt(identifier, 10, 64) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
return err |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if id == 0 { |
|
|
|
|
|
|
|
return nil |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
info, err := s.authInfoService.GetAuthInfo(ctx, &login.GetAuthInfoQuery{UserId: id}) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
// we ignore errors when a user don't have external user auth
|
|
|
|
|
|
|
|
if !errors.Is(err, user.ErrUserNotFound) { |
|
|
|
|
|
|
|
s.logger.FromContext(ctx).Error("Failed to fetch auth info", "userId", id, "error", err) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return nil |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
claims.AuthenticatedBy = info.AuthModule |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return nil |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (s *Service) hook(ctx context.Context, identity *authn.Identity, _ *authn.Request) error { |
|
|
|
func (s *Service) hook(ctx context.Context, identity *authn.Identity, _ *authn.Request) error { |
|
|
|
// FIXME(kalleep): we should probably lazy load this
|
|
|
|
// FIXME(kalleep): we should probably lazy load this
|
|
|
|
token, err := s.SignIdentity(ctx, identity) |
|
|
|
token, err := s.SignIdentity(ctx, identity) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
namespace, id := identity.GetNamespacedID() |
|
|
|
namespace, id := identity.GetNamespacedID() |
|
|
|
s.logger.Error("Failed to sign id token", "err", err, "namespace", namespace, "id", id) |
|
|
|
s.logger.FromContext(ctx).Error("Failed to sign id token", "err", err, "namespace", namespace, "id", id) |
|
|
|
// for now don't return error so we don't break authentication from this hook
|
|
|
|
// for now don't return error so we don't break authentication from this hook
|
|
|
|
return nil |
|
|
|
return nil |
|
|
|
} |
|
|
|
} |
|
|
|