IDForwarding: Add service and a local signer (#75423)

* IDForwarding: Add service for handling id token and create a local signer
---------

Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>
This commit is contained in:
Karl Persson 2023-09-27 11:36:23 +02:00 committed by GitHub
parent 5983bcec86
commit b50f1e15a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 147 additions and 0 deletions

View File

@ -43,6 +43,8 @@ import (
"github.com/grafana/grafana/pkg/services/annotations/annotationsimpl"
"github.com/grafana/grafana/pkg/services/anonymous/anonimpl/anonstore"
"github.com/grafana/grafana/pkg/services/apikey/apikeyimpl"
"github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/auth/idimpl"
"github.com/grafana/grafana/pkg/services/auth/jwt"
"github.com/grafana/grafana/pkg/services/authn/authnimpl"
"github.com/grafana/grafana/pkg/services/cleanup"
@ -361,6 +363,8 @@ var wireBasicSet = wire.NewSet(
loggermw.Provide,
signingkeysimpl.ProvideEmbeddedSigningKeysService,
wire.Bind(new(signingkeys.Service), new(*signingkeysimpl.Service)),
idimpl.ProvideService,
wire.Bind(new(auth.IDService), new(*idimpl.Service)),
grafanaapiserver.WireSet,
apiregistry.WireSet,
)

View File

@ -18,6 +18,7 @@ import (
"github.com/grafana/grafana/pkg/services/anonymous/anonimpl"
"github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/auth/authimpl"
"github.com/grafana/grafana/pkg/services/auth/idimpl"
"github.com/grafana/grafana/pkg/services/caching"
"github.com/grafana/grafana/pkg/services/datasources/guardian"
"github.com/grafana/grafana/pkg/services/encryption"
@ -91,6 +92,8 @@ var wireExtsBasicSet = wire.NewSet(
wire.Bind(new(caching.CachingService), new(*caching.OSSCachingService)),
secretsMigrator.ProvideSecretsMigrator,
wire.Bind(new(secrets.Migrator), new(*secretsMigrator.SecretsMigrator)),
idimpl.ProvideLocalSigner,
wire.Bind(new(auth.IDSigner), new(*idimpl.LocalSigner)),
)
var wireExtsSet = wire.NewSet(

21
pkg/services/auth/id.go Normal file
View File

@ -0,0 +1,21 @@
package auth
import (
"context"
"github.com/go-jose/go-jose/v3/jwt"
"github.com/grafana/grafana/pkg/services/auth/identity"
)
type IDService interface {
// SignIdentity signs a id token for provided identity that can be forwarded to plugins and external services
SignIdentity(ctx context.Context, identity identity.Requester) (string, error)
}
type IDSigner interface {
SignIDToken(ctx context.Context, claims *IDClaims) (string, error)
}
type IDClaims struct {
jwt.Claims
}

View File

@ -0,0 +1,74 @@
package idimpl
import (
"context"
"fmt"
"strconv"
"time"
"github.com/go-jose/go-jose/v3/jwt"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/remotecache"
"github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/setting"
)
const (
cachePrefix = "id-token"
tokenTTL = 1 * time.Hour
cacheTTL = 58 * time.Minute
)
var _ auth.IDService = (*Service)(nil)
func ProvideService(cfg *setting.Cfg, signer auth.IDSigner, cache remotecache.CacheStorage) *Service {
return &Service{cfg, log.New("id-service"), signer, cache}
}
type Service struct {
cfg *setting.Cfg
logger log.Logger
signer auth.IDSigner
cache remotecache.CacheStorage
}
func (s *Service) SignIdentity(ctx context.Context, id identity.Requester) (string, error) {
namespace, identifier := id.GetNamespacedID()
cacheKey := prefixCacheKey(id.GetCacheKey())
cachedToken, err := s.cache.Get(ctx, cacheKey)
if err == nil {
s.logger.Debug("Cached token found", "namespace", namespace, "id", identifier)
return string(cachedToken), nil
}
s.logger.Debug("Sign new id token", "namespace", namespace, "id", identifier)
now := time.Now()
token, err := s.signer.SignIDToken(ctx, &auth.IDClaims{
Claims: jwt.Claims{
ID: identifier,
Issuer: s.cfg.AppURL,
Audience: jwt.Audience{strconv.FormatInt(id.GetOrgID(), 10)},
Subject: fmt.Sprintf("%s:%s", namespace, identifier),
Expiry: jwt.NewNumericDate(now.Add(tokenTTL)),
IssuedAt: jwt.NewNumericDate(now),
},
})
if err != nil {
return "", err
}
if err := s.cache.Set(ctx, cacheKey, []byte(token), cacheTTL); err != nil {
s.logger.Error("failed to set cache", "error", err)
}
return token, nil
}
func prefixCacheKey(key string) string {
return fmt.Sprintf("%s-%s", cachePrefix, key)
}

View File

@ -0,0 +1,45 @@
package idimpl
import (
"context"
"github.com/go-jose/go-jose/v3"
"github.com/go-jose/go-jose/v3/jwt"
"github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/signingkeys"
)
var _ auth.IDSigner = (*LocalSigner)(nil)
func ProvideLocalSigner(keyService signingkeys.Service) (*LocalSigner, error) {
key := keyService.GetServerPrivateKey() // FIXME: replace with signing specific key
signer, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: key}, &jose.SignerOptions{
ExtraHeaders: map[jose.HeaderKey]interface{}{
"kid": "default", // FIXME: replace with specific key id
},
})
if err != nil {
return nil, err
}
return &LocalSigner{
signer: signer,
}, nil
}
type LocalSigner struct {
signer jose.Signer
}
func (s *LocalSigner) SignIDToken(ctx context.Context, claims *auth.IDClaims) (string, error) {
builder := jwt.Signed(s.signer).Claims(claims.Claims)
token, err := builder.CompactSerialize()
if err != nil {
return "", err
}
return token, nil
}