diff --git a/go.mod b/go.mod index a38869422bf..c659250cb7e 100644 --- a/go.mod +++ b/go.mod @@ -484,8 +484,12 @@ require k8s.io/code-generator v0.29.1 // @grafana/grafana-app-platform-squad require github.com/spyzhov/ajson v0.9.0 // @grafana/grafana-app-platform-squad +require github.com/fullstorydev/grpchan v1.1.1 // @grafana/backend-platform + require ( + github.com/bufbuild/protocompile v0.4.0 // indirect github.com/grafana/sqlds/v3 v3.2.0 // indirect + github.com/jhump/protoreflect v1.15.1 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mithrandie/csvq v1.17.10 // indirect github.com/mithrandie/csvq-driver v1.6.8 // indirect diff --git a/go.sum b/go.sum index 7131d991c59..f9a30051631 100644 --- a/go.sum +++ b/go.sum @@ -647,18 +647,13 @@ github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0 github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.8.0 h1:9kDVnTz3vbfweTqAUmk/a/pH5pWFCHtvRpHYC0G/dcA= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.8.0/go.mod h1:3Ug6Qzto9anB6mGlEdgYMDF5zHQ+wwhEaYR4s17PHMw= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1 h1:lGlwhPtrX6EVml1hO0ivjkUxsSyl4dsiw9qcA1k/3IQ= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1/go.mod h1:RKUqNu35KJYcVG/fqTRqmuXJZYNhYkBrnC/hX7yGbTA= github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0 h1:BMAjVKJM0U/CYF27gA0ZMmXGkOcvfFtD0oHVZ1TIPRI= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0/go.mod h1:1fXstnBMas5kzG+S3q8UoJcmyU6nUeunJcMDHcRYHhs= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 h1:sO0/P7g68FrryJzljemN+6GTssUXdANk6aJ7T1ZxnsQ= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1/go.mod h1:h8hyGFDsU5HMivxiS2iYFZsgDbU9OnnJ163x5UGVKYo= github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY= github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM= github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1 h1:6oNBlSdi1QqM1PNW7FPA6xOGA5UNsXnkaYZz9vdPGhA= github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI= @@ -709,8 +704,6 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e h1:NeAW1fUYUEWhft7pkxDf6WoUvEZJ/uOKsvtpjLnn8MU= github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o= -github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 h1:WpB/QDNLpMw72xHJc34BNNykqSOeEJDAWkhf0u12/Jk= -github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 h1:DzHpqpoJVaCgOUdVHxE8QB52S6NiVdDQvGlny1qvPqA= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -1222,6 +1215,8 @@ github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbS github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fsouza/fake-gcs-server v1.7.0/go.mod h1:5XIRs4YvwNbNoz+1JF8j6KLAyDh7RHGAyAK3EP2EsNk= +github.com/fullstorydev/grpchan v1.1.1 h1:heQqIJlAv5Cnks9a70GRL2EJke6QQoUB25VGR6TZQas= +github.com/fullstorydev/grpchan v1.1.1/go.mod h1:f4HpiV8V6htfY/K44GWV1ESQzHBTq7DinhzqQ95lpgc= github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= github.com/gchaincl/sqlhooks v1.3.0 h1:yKPXxW9a5CjXaVf2HkQn6wn7TZARvbAOAelr3H8vK2Y= github.com/gchaincl/sqlhooks v1.3.0/go.mod h1:9BypXnereMT0+Ys8WGWHqzgkkOfHIhyeUCqXC24ra34= @@ -1706,8 +1701,6 @@ github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= -github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-migrate/migrate/v4 v4.7.0 h1:gONcHxHApDTKXDyLH/H97gEHmpu1zcnnbAaq2zgrPrs= @@ -1916,8 +1909,6 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gotestyourself/gotestyourself v1.3.0/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= -github.com/grafana/alerting v0.0.0-20240201111525-bd6396bbe2e1 h1:xxtCQ02pBCzzr1GNZo2fUVrJLQZ9X/vp6rigDRN6PMo= -github.com/grafana/alerting v0.0.0-20240201111525-bd6396bbe2e1/go.mod h1:WMFwxILmCOpaeHe9xP/2v7fxRMStGcW4t2Iuc97Oi6k= github.com/grafana/alerting v0.0.0-20240202155917-ec2c02267fa5 h1:gt514QzadNdnaNKm4DcmDo3ph0M7Hhg1+sdMEhPFVrA= github.com/grafana/alerting v0.0.0-20240202155917-ec2c02267fa5/go.mod h1:WMFwxILmCOpaeHe9xP/2v7fxRMStGcW4t2Iuc97Oi6k= github.com/grafana/codejen v0.0.3 h1:tAWxoTUuhgmEqxJPOLtJoxlPBbMULFwKFOcRsPRPXDw= @@ -1936,8 +1927,6 @@ github.com/grafana/gofpdf v0.0.0-20231002120153-857cc45be447 h1:jxJJ5z0GxqhWFbQU github.com/grafana/gofpdf v0.0.0-20231002120153-857cc45be447/go.mod h1:IxsY6mns6Q5sAnWcrptrgUrSglTZJXH/kXr9nbpb/9I= github.com/grafana/grafana-aws-sdk v0.23.1 h1:YP6DqzB36fp8fXno0r+X9BxNB3apNfJnQxu8tdhYMH8= github.com/grafana/grafana-aws-sdk v0.23.1/go.mod h1:iTbW395xv26qy6L17SjtZlVwxQTIZbmupBTe0sPHv7k= -github.com/grafana/grafana-azure-sdk-go v1.11.0 h1:nc6MgOZ5fIaxvBfZjYU5rSqB4zaD7rlU8BqnGcXZtWk= -github.com/grafana/grafana-azure-sdk-go v1.11.0/go.mod h1:5a3FuG2lEsYNop9HDNgTO1bx4ExCgsjvrFhpuqolYAU= github.com/grafana/grafana-azure-sdk-go v1.12.0 h1:q71M2QxMlBqRZOXc5mFAycJWuZqQ3hPTzVEo1r3CUTY= github.com/grafana/grafana-azure-sdk-go v1.12.0/go.mod h1:SAlwLdEuox4vw8ZaeQwnepYXnhznnQQdstJbcw8LH68= github.com/grafana/grafana-google-sdk-go v0.1.0 h1:LKGY8z2DSxKjYfr2flZsWgTRTZ6HGQbTqewE3JvRaNA= @@ -2181,7 +2170,11 @@ github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJk github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +github.com/jhump/gopoet v0.0.0-20190322174617-17282ff210b3/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI= +github.com/jhump/gopoet v0.1.0/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI= +github.com/jhump/goprotoc v0.5.0/go.mod h1:VrbvcYrQOrTi3i0Vf+m+oqQWk9l72mjkJCYo7UvLHRQ= github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= +github.com/jhump/protoreflect v1.11.0/go.mod h1:U7aMIjN0NWq9swDP7xDdoMfRHb35uiuTd3Z9nFXJf5E= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= @@ -2662,7 +2655,6 @@ github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFu github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= -github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= diff --git a/pkg/services/apiserver/service.go b/pkg/services/apiserver/service.go index 712d0ad5af4..c547fb21e10 100644 --- a/pkg/services/apiserver/service.go +++ b/pkg/services/apiserver/service.go @@ -240,11 +240,13 @@ func (s *service) start(ctx context.Context) error { return err } - store, err := sqlstash.ProvideSQLEntityServer(eDB) + storeServer, err := sqlstash.ProvideSQLEntityServer(eDB) if err != nil { return err } + store := entity.NewEntityStoreClientLocal(storeServer) + serverConfig.Config.RESTOptionsGetter = entitystorage.NewRESTOptionsGetter(s.cfg, store, o.RecommendedOptions.Etcd.StorageConfig.Codec) case grafanaapiserveroptions.StorageTypeUnifiedGrpc: @@ -259,7 +261,7 @@ func (s *service) start(ctx context.Context) error { // defer conn.Close() // Create a client instance - store := entity.NewEntityStoreClientWrapper(conn) + store := entity.NewEntityStoreClientGRPC(conn) serverConfig.Config.RESTOptionsGetter = entitystorage.NewRESTOptionsGetter(s.cfg, store, o.RecommendedOptions.Etcd.StorageConfig.Codec) diff --git a/pkg/services/apiserver/storage/entity/restoptions.go b/pkg/services/apiserver/storage/entity/restoptions.go index 78bc96984f8..58119cc87ac 100644 --- a/pkg/services/apiserver/storage/entity/restoptions.go +++ b/pkg/services/apiserver/storage/entity/restoptions.go @@ -24,11 +24,11 @@ var _ generic.RESTOptionsGetter = (*RESTOptionsGetter)(nil) type RESTOptionsGetter struct { cfg *setting.Cfg - store entityStore.EntityStoreServer + store entityStore.EntityStoreClient Codec runtime.Codec } -func NewRESTOptionsGetter(cfg *setting.Cfg, store entityStore.EntityStoreServer, codec runtime.Codec) *RESTOptionsGetter { +func NewRESTOptionsGetter(cfg *setting.Cfg, store entityStore.EntityStoreClient, codec runtime.Codec) *RESTOptionsGetter { return &RESTOptionsGetter{ cfg: cfg, store: store, diff --git a/pkg/services/apiserver/storage/entity/storage.go b/pkg/services/apiserver/storage/entity/storage.go index ce623fb7e7a..b11a029d6a2 100644 --- a/pkg/services/apiserver/storage/entity/storage.go +++ b/pkg/services/apiserver/storage/entity/storage.go @@ -36,7 +36,7 @@ const MaxUpdateAttempts = 1 // Storage implements storage.Interface and storage resources as JSON files on disk. type Storage struct { config *storagebackend.ConfigForResource - store entityStore.EntityStoreServer + store entityStore.EntityStoreClient gr schema.GroupResource codec runtime.Codec keyFunc func(obj runtime.Object) (string, error) @@ -52,7 +52,7 @@ type Storage struct { func NewStorage( config *storagebackend.ConfigForResource, gr schema.GroupResource, - store entityStore.EntityStoreServer, + store entityStore.EntityStoreClient, codec runtime.Codec, keyFunc func(obj runtime.Object) (string, error), newFunc func() runtime.Object, diff --git a/pkg/services/store/entity/client_wrapper.go b/pkg/services/store/entity/client_wrapper.go index d4dadae9414..18d151e9851 100644 --- a/pkg/services/store/entity/client_wrapper.go +++ b/pkg/services/store/entity/client_wrapper.go @@ -1,94 +1,30 @@ package entity import ( - context "context" - "strconv" + "github.com/fullstorydev/grpchan" + "github.com/fullstorydev/grpchan/inprocgrpc" + grpcAuth "github.com/grpc-ecosystem/go-grpc-middleware/auth" + "google.golang.org/grpc" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - "google.golang.org/grpc/metadata" - status "google.golang.org/grpc/status" - - "github.com/grafana/grafana/pkg/infra/appcontext" + grpcUtils "github.com/grafana/grafana/pkg/services/store/entity/grpc" ) -var _ EntityStoreServer = (*entityStoreClientWrapper)(nil) +func NewEntityStoreClientLocal(server EntityStoreServer) EntityStoreClient { + channel := &inprocgrpc.Channel{} -// wrapper for EntityStoreClient that implements EntityStore interface -type entityStoreClientWrapper struct { - EntityStoreClient + auth := &grpcUtils.Authenticator{} + + channel.RegisterService( + grpchan.InterceptServer( + &EntityStore_ServiceDesc, + grpcAuth.UnaryServerInterceptor(auth.Authenticate), + grpcAuth.StreamServerInterceptor(auth.Authenticate), + ), + server, + ) + return NewEntityStoreClient(grpchan.InterceptClientConn(channel, grpcUtils.UnaryClientInterceptor, grpcUtils.StreamClientInterceptor)) } -func (c *entityStoreClientWrapper) Read(ctx context.Context, in *ReadEntityRequest) (*Entity, error) { - ctx, err := c.wrapContext(ctx) - if err != nil { - return nil, err - } - return c.EntityStoreClient.Read(ctx, in) -} -func (c *entityStoreClientWrapper) BatchRead(ctx context.Context, in *BatchReadEntityRequest) (*BatchReadEntityResponse, error) { - ctx, err := c.wrapContext(ctx) - if err != nil { - return nil, err - } - return c.EntityStoreClient.BatchRead(ctx, in) -} -func (c *entityStoreClientWrapper) Create(ctx context.Context, in *CreateEntityRequest) (*CreateEntityResponse, error) { - ctx, err := c.wrapContext(ctx) - if err != nil { - return nil, err - } - return c.EntityStoreClient.Create(ctx, in) -} -func (c *entityStoreClientWrapper) Update(ctx context.Context, in *UpdateEntityRequest) (*UpdateEntityResponse, error) { - ctx, err := c.wrapContext(ctx) - if err != nil { - return nil, err - } - return c.EntityStoreClient.Update(ctx, in) -} -func (c *entityStoreClientWrapper) Delete(ctx context.Context, in *DeleteEntityRequest) (*DeleteEntityResponse, error) { - ctx, err := c.wrapContext(ctx) - if err != nil { - return nil, err - } - return c.EntityStoreClient.Delete(ctx, in) -} -func (c *entityStoreClientWrapper) History(ctx context.Context, in *EntityHistoryRequest) (*EntityHistoryResponse, error) { - ctx, err := c.wrapContext(ctx) - if err != nil { - return nil, err - } - return c.EntityStoreClient.History(ctx, in) -} -func (c *entityStoreClientWrapper) List(ctx context.Context, in *EntityListRequest) (*EntityListResponse, error) { - ctx, err := c.wrapContext(ctx) - if err != nil { - return nil, err - } - return c.EntityStoreClient.List(ctx, in) -} -func (c *entityStoreClientWrapper) Watch(*EntityWatchRequest, EntityStore_WatchServer) error { - return status.Errorf(codes.Unimplemented, "method Watch not implemented") -} - -func (c *entityStoreClientWrapper) wrapContext(ctx context.Context) (context.Context, error) { - user, err := appcontext.User(ctx) - if err != nil { - return nil, err - } - - // set grpc metadata into the context to pass to the grpc server - ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs( - "grafana-idtoken", user.IDToken, - "grafana-userid", strconv.FormatInt(user.UserID, 10), - "grafana-orgid", strconv.FormatInt(user.OrgID, 10), - "grafana-login", user.Login, - )) - - return ctx, nil -} - -func NewEntityStoreClientWrapper(cc grpc.ClientConnInterface) EntityStoreServer { - return &entityStoreClientWrapper{&entityStoreClient{cc}} +func NewEntityStoreClientGRPC(channel *grpc.ClientConn) EntityStoreClient { + return NewEntityStoreClient(grpchan.InterceptClientConn(channel, grpcUtils.UnaryClientInterceptor, grpcUtils.StreamClientInterceptor)) } diff --git a/pkg/services/store/entity/grpc/authenticator.go b/pkg/services/store/entity/grpc/authenticator.go new file mode 100644 index 00000000000..c8e52721f92 --- /dev/null +++ b/pkg/services/store/entity/grpc/authenticator.go @@ -0,0 +1,97 @@ +package grpc + +import ( + "context" + "fmt" + "strconv" + + "github.com/grafana/grafana/pkg/infra/appcontext" + "github.com/grafana/grafana/pkg/services/grpcserver/interceptors" + "github.com/grafana/grafana/pkg/services/user" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" +) + +type Authenticator struct{} + +func (f *Authenticator) Authenticate(ctx context.Context) (context.Context, error) { + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + return nil, fmt.Errorf("no metadata found") + } + + // TODO: use id token instead of these fields + login := md.Get("grafana-login")[0] + if login == "" { + return nil, fmt.Errorf("no login found in context") + } + userID, err := strconv.ParseInt(md.Get("grafana-userid")[0], 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid user id: %w", err) + } + orgID, err := strconv.ParseInt(md.Get("grafana-orgid")[0], 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid org id: %w", err) + } + + // TODO: validate id token + /* + idToken := md.Get("grafana-idtoken")[0] + if idToken == "" { + return nil, fmt.Errorf("no id token found in context") + } + jwtToken, err := jwt.ParseSigned(idToken) + if err != nil { + return nil, fmt.Errorf("invalid id token: %w", err) + } + claims := jwt.Claims{} + err = jwtToken.UnsafeClaimsWithoutVerification(&claims) + if err != nil { + return nil, fmt.Errorf("invalid id token: %w", err) + } + // fmt.Printf("JWT CLAIMS: %+v\n", claims) + */ + + return appcontext.WithUser(ctx, &user.SignedInUser{ + Login: login, + UserID: userID, + OrgID: orgID, + }), nil +} + +var _ interceptors.Authenticator = (*Authenticator)(nil) + +func UnaryClientInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { + ctx, err := WrapContext(ctx) + if err != nil { + return err + } + return invoker(ctx, method, req, reply, cc, opts...) +} + +var _ grpc.UnaryClientInterceptor = UnaryClientInterceptor + +func StreamClientInterceptor(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) { + ctx, err := WrapContext(ctx) + if err != nil { + return nil, err + } + return streamer(ctx, desc, cc, method, opts...) +} + +var _ grpc.StreamClientInterceptor = StreamClientInterceptor + +func WrapContext(ctx context.Context) (context.Context, error) { + user, err := appcontext.User(ctx) + if err != nil { + return ctx, err + } + + // set grpc metadata into the context to pass to the grpc server + return metadata.NewOutgoingContext(ctx, metadata.Pairs( + "grafana-idtoken", user.IDToken, + "grafana-userid", strconv.FormatInt(user.UserID, 10), + "grafana-orgid", strconv.FormatInt(user.OrgID, 10), + "grafana-login", user.Login, + )), nil +} diff --git a/pkg/services/store/entity/server/service.go b/pkg/services/store/entity/server/service.go index c5ae0b429f9..1f08551cfad 100644 --- a/pkg/services/store/entity/server/service.go +++ b/pkg/services/store/entity/server/service.go @@ -2,15 +2,10 @@ package server import ( "context" - "fmt" - "strconv" - "github.com/go-jose/go-jose/v3/jwt" "github.com/grafana/dskit/services" "github.com/prometheus/client_golang/prometheus" - "google.golang.org/grpc/metadata" - "github.com/grafana/grafana/pkg/infra/appcontext" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/modules" "github.com/grafana/grafana/pkg/registry" @@ -19,8 +14,8 @@ import ( "github.com/grafana/grafana/pkg/services/grpcserver/interceptors" "github.com/grafana/grafana/pkg/services/store/entity" "github.com/grafana/grafana/pkg/services/store/entity/db/dbimpl" + "github.com/grafana/grafana/pkg/services/store/entity/grpc" "github.com/grafana/grafana/pkg/services/store/entity/sqlstash" - "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" ) @@ -58,53 +53,6 @@ type service struct { authenticator interceptors.Authenticator } -type Authenticator struct{} - -func (f *Authenticator) Authenticate(ctx context.Context) (context.Context, error) { - md, ok := metadata.FromIncomingContext(ctx) - if !ok { - return nil, fmt.Errorf("no metadata found") - } - - // TODO: use id token instead of these fields - login := md.Get("grafana-login")[0] - if login == "" { - return nil, fmt.Errorf("no login found in context") - } - userID, err := strconv.ParseInt(md.Get("grafana-userid")[0], 10, 64) - if err != nil { - return nil, fmt.Errorf("invalid user id: %w", err) - } - orgID, err := strconv.ParseInt(md.Get("grafana-orgid")[0], 10, 64) - if err != nil { - return nil, fmt.Errorf("invalid org id: %w", err) - } - - // TODO: validate id token - idToken := md.Get("grafana-idtoken")[0] - if idToken == "" { - return nil, fmt.Errorf("no id token found in context") - } - jwtToken, err := jwt.ParseSigned(idToken) - if err != nil { - return nil, fmt.Errorf("invalid id token: %w", err) - } - claims := jwt.Claims{} - err = jwtToken.UnsafeClaimsWithoutVerification(&claims) - if err != nil { - return nil, fmt.Errorf("invalid id token: %w", err) - } - // fmt.Printf("JWT CLAIMS: %+v\n", claims) - - return appcontext.WithUser(ctx, &user.SignedInUser{ - Login: login, - UserID: userID, - OrgID: orgID, - }), nil -} - -var _ interceptors.Authenticator = (*Authenticator)(nil) - func ProvideService( cfg *setting.Cfg, features featuremgmt.FeatureToggles, @@ -114,7 +62,7 @@ func ProvideService( return nil, err } - authn := &Authenticator{} + authn := &grpc.Authenticator{} s := &service{ config: newConfig(cfg),