From 92aba937a963b8d2122fc7612c2003e7527b115e Mon Sep 17 00:00:00 2001 From: Claudiu Dragalina-Paraipan Date: Thu, 1 Aug 2024 18:32:19 +0300 Subject: [PATCH] authn: client side updates Co-Authored-By: Gabriel MABILLE --- pkg/services/apiserver/service.go | 5 +- pkg/services/authn/grpcutils/config.go | 21 +++++ .../unified/resource/client_wrapper.go | 83 ++++++++++++++++++- .../resource/grpc/inproc-authenticator.go | 27 ++++++ 4 files changed, 132 insertions(+), 4 deletions(-) create mode 100644 pkg/services/authn/grpcutils/config.go create mode 100644 pkg/storage/unified/resource/grpc/inproc-authenticator.go diff --git a/pkg/services/apiserver/service.go b/pkg/services/apiserver/service.go index 0afa1621fc9..2b3a6e4257b 100644 --- a/pkg/services/apiserver/service.go +++ b/pkg/services/apiserver/service.go @@ -288,7 +288,10 @@ func (s *service) start(ctx context.Context) error { } // Create a client instance - client := resource.NewResourceStoreClientGRPC(conn) + client, err := resource.NewResourceStoreClientGRPC(conn) + if err != nil { + return err + } serverConfig.Config.RESTOptionsGetter = apistore.NewRESTOptionsGetterForClient(client, o.RecommendedOptions.Etcd.StorageConfig) case grafanaapiserveroptions.StorageTypeLegacy: diff --git a/pkg/services/authn/grpcutils/config.go b/pkg/services/authn/grpcutils/config.go new file mode 100644 index 00000000000..488f9a0590c --- /dev/null +++ b/pkg/services/authn/grpcutils/config.go @@ -0,0 +1,21 @@ +package grpcutils + +import ( + "github.com/grafana/grafana/pkg/setting" +) + +type GrpcClientConfig struct { + Token string + TokenExchangeURL string + TokenNamespace string +} + +func ReadCfg(cfg *setting.Cfg) *GrpcClientConfig { + section := cfg.SectionWithEnvOverrides("grpc_client_authentication") + + return &GrpcClientConfig{ + Token: section.Key("token").MustString(""), + TokenExchangeURL: section.Key("token_exchange_url").MustString(""), + TokenNamespace: section.Key("token_namespace").MustString("stack-" + cfg.StackID), + } +} diff --git a/pkg/storage/unified/resource/client_wrapper.go b/pkg/storage/unified/resource/client_wrapper.go index 7ad67e937d8..a3d02a0beeb 100644 --- a/pkg/storage/unified/resource/client_wrapper.go +++ b/pkg/storage/unified/resource/client_wrapper.go @@ -1,18 +1,29 @@ package resource import ( + "context" + "fmt" + "github.com/fullstorydev/grpchan" "github.com/fullstorydev/grpchan/inprocgrpc" + authnlib "github.com/grafana/authlib/authn" + authzlib "github.com/grafana/authlib/authz" grpcAuth "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth" "google.golang.org/grpc" + "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/services/authn/grpcutils" grpcUtils "github.com/grafana/grafana/pkg/storage/unified/resource/grpc" ) +// TODO: decide on the audience for the resource store +const resourceStoreAudience = "resourceStore" + func NewLocalResourceStoreClient(server ResourceStoreServer) ResourceStoreClient { + // scenario: local in-proc channel := &inprocgrpc.Channel{} - auth := &grpcUtils.Authenticator{} + auth := &grpcUtils.InProcAuthenticator{} channel.RegisterService( grpchan.InterceptServer( @@ -25,6 +36,72 @@ func NewLocalResourceStoreClient(server ResourceStoreServer) ResourceStoreClient return NewResourceStoreClient(grpchan.InterceptClientConn(channel, grpcUtils.UnaryClientInterceptor, grpcUtils.StreamClientInterceptor)) } -func NewResourceStoreClientGRPC(channel *grpc.ClientConn) ResourceStoreClient { - return NewResourceStoreClient(grpchan.InterceptClientConn(channel, grpcUtils.UnaryClientInterceptor, grpcUtils.StreamClientInterceptor)) +func NewResourceStoreClientGRPC(conn *grpc.ClientConn) (ResourceStoreClient, error) { + // scenario: remote on-prem + clientInterceptor, err := authnlib.NewGrpcClientInterceptor( + &authnlib.GrpcClientConfig{}, + authnlib.WithDisableAccessTokenOption(), + authnlib.WithIDTokenExtractorOption(idTokenExtractor), + authnlib.WithMetadataExtractorOption(orgIdExtractor), + ) + if err != nil { + return nil, err + } + + return NewResourceStoreClient( + grpchan.InterceptClientConn( + conn, + clientInterceptor.UnaryClientInterceptor, + clientInterceptor.StreamClientInterceptor, + )), + nil +} + +func NewResourceStoreClientCloud(conn *grpc.ClientConn, cfg *grpcutils.GrpcClientConfig) (ResourceStoreClient, error) { + // scenario: remote cloud + grpcClientConfig := authnlib.GrpcClientConfig{ + TokenClientConfig: &authnlib.TokenExchangeConfig{ + Token: cfg.Token, + TokenExchangeURL: cfg.TokenExchangeURL, + }, + TokenRequest: &authnlib.TokenExchangeRequest{ + Namespace: cfg.TokenNamespace, + Audiences: []string{resourceStoreAudience}, + }, + } + + clientInterceptor, err := authnlib.NewGrpcClientInterceptor( + &grpcClientConfig, + authnlib.WithIDTokenExtractorOption(idTokenExtractor), + authnlib.WithMetadataExtractorOption(orgIdExtractor), + ) + if err != nil { + return nil, err + } + + return NewResourceStoreClient( + grpchan.InterceptClientConn( + conn, + clientInterceptor.UnaryClientInterceptor, + clientInterceptor.StreamClientInterceptor, + )), + nil +} + +func idTokenExtractor(ctx context.Context) (string, error) { + requester, err := identity.GetRequester(ctx) + if err != nil { + return "", err + } + + return requester.GetIDToken(), nil +} + +func orgIdExtractor(ctx context.Context) (key string, values []string, err error) { + requester, err := identity.GetRequester(ctx) + if err != nil { + return "", nil, err + } + + return authzlib.DefaultStackIDMetadataKey, []string{fmt.Sprintf("%d", requester.GetOrgID())}, nil } diff --git a/pkg/storage/unified/resource/grpc/inproc-authenticator.go b/pkg/storage/unified/resource/grpc/inproc-authenticator.go new file mode 100644 index 00000000000..47302723e67 --- /dev/null +++ b/pkg/storage/unified/resource/grpc/inproc-authenticator.go @@ -0,0 +1,27 @@ +package grpc + +import ( + "context" + "fmt" + + authnlib "github.com/grafana/authlib/authn" + + "github.com/grafana/grafana/pkg/apimachinery/identity" +) + +type InProcAuthenticator struct{} + +func (f *InProcAuthenticator) Authenticate(ctx context.Context) (context.Context, error) { + r, err := identity.GetRequester(ctx) + if err != nil { + return nil, err + } + + idClaims := r.GetIDClaims() + if idClaims == nil { + return nil, fmt.Errorf("no id claims found") + } + + callerAuthInfo := authnlib.CallerAuthInfo{IDTokenClaims: idClaims} + return authnlib.AddCallerAuthInfoToContext(ctx, callerAuthInfo), nil +}