diff --git a/pkg/services/apiserver/options/storage.go b/pkg/services/apiserver/options/storage.go index 088bf5e5fe8..7cef0d0bc7f 100644 --- a/pkg/services/apiserver/options/storage.go +++ b/pkg/services/apiserver/options/storage.go @@ -19,6 +19,7 @@ const ( StorageTypeLegacy StorageType = "legacy" StorageTypeUnified StorageType = "unified" StorageTypeUnifiedGrpc StorageType = "unified-grpc" + // TODO(drclau): add StorageTypeUnifiedCloud? ) type StorageOptions struct { diff --git a/pkg/services/apiserver/service.go b/pkg/services/apiserver/service.go index 2b3a6e4257b..c3dc723dd7c 100644 --- a/pkg/services/apiserver/service.go +++ b/pkg/services/apiserver/service.go @@ -288,6 +288,7 @@ func (s *service) start(ctx context.Context) error { } // Create a client instance + // TODO(drclau): differentiate based on grpc_mode (e.g. on-prem vs. cloud) client, err := resource.NewResourceStoreClientGRPC(conn) if err != nil { return err diff --git a/pkg/services/authn/grpcutils/config.go b/pkg/services/authn/grpcutils/config.go index 488f9a0590c..30e123b93b6 100644 --- a/pkg/services/authn/grpcutils/config.go +++ b/pkg/services/authn/grpcutils/config.go @@ -1,6 +1,8 @@ package grpcutils import ( + "fmt" + "github.com/grafana/grafana/pkg/setting" ) @@ -10,7 +12,7 @@ type GrpcClientConfig struct { TokenNamespace string } -func ReadCfg(cfg *setting.Cfg) *GrpcClientConfig { +func ReadGrpcClientConfig(cfg *setting.Cfg) *GrpcClientConfig { section := cfg.SectionWithEnvOverrides("grpc_client_authentication") return &GrpcClientConfig{ @@ -19,3 +21,41 @@ func ReadCfg(cfg *setting.Cfg) *GrpcClientConfig { TokenNamespace: section.Key("token_namespace").MustString("stack-" + cfg.StackID), } } + +type Mode string + +func (s Mode) IsValid() bool { + switch s { + case ModeGRPC, ModeInProc, ModeCloud: + return true + } + return false +} + +const ( + ModeGRPC Mode = "grpc" + ModeInProc Mode = "inproc" + ModeCloud Mode = "cloud" +) + +type GrpcServerConfig struct { + Mode Mode + + SigningKeysURL string + AllowedAudiences []string +} + +func ReadGprcServerConfig(cfg *setting.Cfg) (*GrpcServerConfig, error) { + section := cfg.SectionWithEnvOverrides("grpc_server_authentication") + + mode := Mode(section.Key("mode").MustString(string(ModeGRPC))) + if !mode.IsValid() { + return nil, fmt.Errorf("grpc_server_authentication: invalid mode %q", mode) + } + + return &GrpcServerConfig{ + Mode: mode, + SigningKeysURL: section.Key("signing_keys_url").MustString(""), + AllowedAudiences: section.Key("allowed_audiences").Strings(","), + }, nil +} diff --git a/pkg/services/authz/server.go b/pkg/services/authz/server.go index 42f0523df98..0540976c748 100644 --- a/pkg/services/authz/server.go +++ b/pkg/services/authz/server.go @@ -44,6 +44,12 @@ func newLegacyServer( return s, nil } +// AuthFuncOverride +// FIXME(drclau): make sure we only run this when app_mode = development +func (s *legacyServer) AuthFuncOverride(ctx context.Context, _ string) (context.Context, error) { + return ctx, nil +} + func (s *legacyServer) Read(ctx context.Context, req *authzv1.ReadRequest) (*authzv1.ReadResponse, error) { ctx, span := s.tracer.Start(ctx, "authz.grpc.Read") defer span.End() diff --git a/pkg/services/grpcserver/service.go b/pkg/services/grpcserver/service.go index f4777b44356..52b9e492010 100644 --- a/pkg/services/grpcserver/service.go +++ b/pkg/services/grpcserver/service.go @@ -6,6 +6,8 @@ import ( "net" "time" + authnlib "github.com/grafana/authlib/authn" + authzlib "github.com/grafana/authlib/authz" "github.com/grafana/dskit/instrument" "github.com/grafana/dskit/middleware" "github.com/grafana/grafana-plugin-sdk-go/backend" @@ -70,12 +72,21 @@ func ProvideService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, authe var opts []grpc.ServerOption + namespaceFmt := authnlib.OnPremNamespaceFormatter + // TODO(drclau): check the actual mode setting here + if cfg.StackID != "" { + namespaceFmt = authnlib.CloudNamespaceFormatter + } + namespaceChecker := authzlib.NewNamespaceAccessChecker(namespaceFmt) + stackIdExtractor := authzlib.MetadataStackIDExtractor(authzlib.DefaultStackIDMetadataKey) + // Default auth is admin token check, but this can be overridden by // services which implement ServiceAuthFuncOverride interface. // See https://github.com/grpc-ecosystem/go-grpc-middleware/blob/main/interceptors/auth/auth.go#L30. opts = append(opts, []grpc.ServerOption{ grpc.ChainUnaryInterceptor( grpcAuth.UnaryServerInterceptor(authenticator.Authenticate), + authzlib.UnaryNamespaceAccessInterceptor(namespaceChecker, stackIdExtractor), interceptors.TracingUnaryInterceptor(tracer), interceptors.LoggingUnaryInterceptor(s.cfg, s.logger), // needs to be registered after tracing interceptor to get trace id middleware.UnaryServerInstrumentInterceptor(grpcRequestDuration), @@ -83,6 +94,7 @@ func ProvideService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, authe grpc.ChainStreamInterceptor( interceptors.TracingStreamInterceptor(tracer), grpcAuth.StreamServerInterceptor(authenticator.Authenticate), + authzlib.StreamNamespaceAccessInterceptor(namespaceChecker, stackIdExtractor), middleware.StreamServerInstrumentInterceptor(grpcRequestDuration), ), }...) diff --git a/pkg/storage/unified/sql/service.go b/pkg/storage/unified/sql/service.go index 8ad9d9bd134..7719d0b509e 100644 --- a/pkg/storage/unified/sql/service.go +++ b/pkg/storage/unified/sql/service.go @@ -3,6 +3,7 @@ package sql import ( "context" + authnlib "github.com/grafana/authlib/authn" "github.com/grafana/dskit/services" "github.com/prometheus/client_golang/prometheus" "google.golang.org/grpc/health/grpc_health_v1" @@ -11,12 +12,12 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/modules" + "github.com/grafana/grafana/pkg/services/authn/grpcutils" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/grpcserver" "github.com/grafana/grafana/pkg/services/grpcserver/interceptors" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/storage/unified/resource" - "github.com/grafana/grafana/pkg/storage/unified/resource/grpc" ) var ( @@ -62,7 +63,38 @@ func ProvideService( return nil, err } - authn := &grpc.Authenticator{} + authCfg, err := grpcutils.ReadGprcServerConfig(cfg) + if err != nil { + return nil, err + } + + grpcAuthCfg := authnlib.GrpcAuthenticatorConfig{ + KeyRetrieverConfig: authnlib.KeyRetrieverConfig{ + SigningKeysURL: authCfg.SigningKeysURL, + }, + VerifierConfig: authnlib.VerifierConfig{ + AllowedAudiences: authCfg.AllowedAudiences, + }, + } + + grpcOpts := []authnlib.GrpcAuthenticatorOption{} + switch authCfg.Mode { + case grpcutils.ModeInProc: + // NOOP: IDTokenClaims are added to ctx client-side + // TODO(drclau): do we need orgId? + case grpcutils.ModeGRPC: + grpcOpts = append(grpcOpts, + authnlib.WithDisableAccessTokenAuthOption(), + authnlib.WithIDTokenAuthOption(true), + ) + case grpcutils.ModeCloud: + grpcOpts = append(grpcOpts, authnlib.WithIDTokenAuthOption(true)) + } + + authn, err := authnlib.NewGrpcAuthenticator( + &grpcAuthCfg, + grpcOpts..., + ) s := &service{ cfg: cfg,