diff --git a/pkg/registry/apis/folders/register.go b/pkg/registry/apis/folders/register.go index e7e163ff156..c5c352facc8 100644 --- a/pkg/registry/apis/folders/register.go +++ b/pkg/registry/apis/folders/register.go @@ -53,6 +53,7 @@ type FolderAPIBuilder struct { accessControl accesscontrol.AccessControl searcher resource.ResourceIndexClient cfg *setting.Cfg + ignoreLegacy bool // skip legacy storage and only use unified storage } func RegisterAPIService(cfg *setting.Cfg, @@ -85,6 +86,14 @@ func RegisterAPIService(cfg *setting.Cfg, return builder } +func NewAPIService() *FolderAPIBuilder { + return &FolderAPIBuilder{ + gv: resourceInfo.GroupVersion(), + namespacer: request.GetNamespaceMapper(nil), + ignoreLegacy: true, + } +} + func (b *FolderAPIBuilder) GetGroupVersion() schema.GroupVersion { return b.gv } @@ -122,6 +131,18 @@ func (b *FolderAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.API scheme := opts.Scheme optsGetter := opts.OptsGetter dualWriteBuilder := opts.DualWriteBuilder + storage := map[string]rest.Storage{} + + if b.ignoreLegacy { + store, err := grafanaregistry.NewRegistryStore(opts.Scheme, resourceInfo, opts.OptsGetter) + if err != nil { + return err + } + storage[resourceInfo.StoragePath()] = store + apiGroupInfo.VersionedResourcesStorageMap[v0alpha1.VERSION] = storage + b.storage = storage[resourceInfo.StoragePath()].(grafanarest.Storage) + return nil + } legacyStore := &legacyStorage{ service: b.folderSvc, @@ -135,7 +156,6 @@ func (b *FolderAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.API opts.StorageOptions(resourceInfo.GroupResource(), apistore.StorageOptions{ RequireDeprecatedInternalID: true}) - storage := map[string]rest.Storage{} storage[resourceInfo.StoragePath()] = legacyStore if optsGetter != nil && dualWriteBuilder != nil { store, err := grafanaregistry.NewRegistryStore(scheme, resourceInfo, optsGetter) diff --git a/pkg/services/apiserver/options/storage.go b/pkg/services/apiserver/options/storage.go index 4841112fedf..8a492cb4001 100644 --- a/pkg/services/apiserver/options/storage.go +++ b/pkg/services/apiserver/options/storage.go @@ -5,11 +5,18 @@ import ( "net" "github.com/spf13/pflag" + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" genericapiserver "k8s.io/apiserver/pkg/server" "k8s.io/apiserver/pkg/server/options" + "github.com/grafana/authlib/authn" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/storage/unified/apistore" + "github.com/grafana/grafana/pkg/storage/unified/resource" ) type StorageType string @@ -28,8 +35,12 @@ type StorageOptions struct { // The desired storage type StorageType StorageType - // For unified-grpc, the address is required - Address string + // For unified-grpc + Address string + GrpcClientAuthenticationToken string + GrpcClientAuthenticationTokenExchangeURL string + GrpcClientAuthenticationTokenNamespace string + GrpcClientAuthenticationAllowInsecure bool // For file storage, this is the requested path DataPath string @@ -47,8 +58,10 @@ type StorageOptions struct { func NewStorageOptions() *StorageOptions { return &StorageOptions{ - StorageType: StorageTypeUnified, - Address: "localhost:10000", + StorageType: StorageTypeUnified, + Address: "localhost:10000", + GrpcClientAuthenticationTokenNamespace: "*", + GrpcClientAuthenticationAllowInsecure: false, } } @@ -56,6 +69,10 @@ func (o *StorageOptions) AddFlags(fs *pflag.FlagSet) { fs.StringVar((*string)(&o.StorageType), "grafana-apiserver-storage-type", string(o.StorageType), "Storage type") fs.StringVar(&o.DataPath, "grafana-apiserver-storage-path", o.DataPath, "Storage path for file storage") fs.StringVar(&o.Address, "grafana-apiserver-storage-address", o.Address, "Remote grpc address endpoint") + fs.StringVar(&o.GrpcClientAuthenticationToken, "grpc-client-authentication-token", o.GrpcClientAuthenticationToken, "Token for grpc client authentication") + fs.StringVar(&o.GrpcClientAuthenticationTokenExchangeURL, "grpc-client-authentication-token-exchange-url", o.GrpcClientAuthenticationTokenExchangeURL, "Token exchange url for grpc client authentication") + fs.StringVar(&o.GrpcClientAuthenticationTokenNamespace, "grpc-client-authentication-token-namespace", o.GrpcClientAuthenticationTokenNamespace, "Token namespace for grpc client authentication") + fs.BoolVar(&o.GrpcClientAuthenticationAllowInsecure, "grpc-client-authentication-allow-insecure", o.GrpcClientAuthenticationAllowInsecure, "Allow insecure grpc client authentication") } func (o *StorageOptions) Validate() []error { @@ -79,11 +96,49 @@ func (o *StorageOptions) Validate() []error { if o.BlobStoreURL != "" && o.StorageType != StorageTypeUnified { errs = append(errs, fmt.Errorf("blob storage is only valid with unified storage")) } + + // Validate grpc client with auth + if o.StorageType == StorageTypeUnifiedGrpc && o.GrpcClientAuthenticationToken != "" { + if o.GrpcClientAuthenticationToken == "" { + errs = append(errs, fmt.Errorf("grpc client auth token is required for unified-grpc storage")) + } + if o.GrpcClientAuthenticationTokenExchangeURL == "" { + errs = append(errs, fmt.Errorf("grpc client auth token exchange url is required for unified-grpc storage")) + } + if o.GrpcClientAuthenticationTokenNamespace == "" { + errs = append(errs, fmt.Errorf("grpc client auth namespace is required for unified-grpc storage")) + } + } return errs } -func (o *StorageOptions) ApplyTo(serverConfig *genericapiserver.RecommendedConfig, etcdOptions *options.EtcdOptions) error { - // TODO: move storage setup here +func (o *StorageOptions) ApplyTo(serverConfig *genericapiserver.RecommendedConfig, etcdOptions *options.EtcdOptions, tracer tracing.Tracer) error { + if o.StorageType != StorageTypeUnifiedGrpc { + return nil + } + conn, err := grpc.NewClient(o.Address, + grpc.WithStatsHandler(otelgrpc.NewClientHandler()), + grpc.WithTransportCredentials(insecure.NewCredentials()), + ) + if err != nil { + return err + } + authCfg := authn.GrpcClientConfig{ + TokenClientConfig: &authn.TokenExchangeConfig{ + Token: o.GrpcClientAuthenticationToken, + TokenExchangeURL: o.GrpcClientAuthenticationTokenExchangeURL, + }, + TokenRequest: &authn.TokenExchangeRequest{ + Audiences: []string{"resourceStore"}, + Namespace: o.GrpcClientAuthenticationTokenNamespace, + }, + } + unified, err := resource.NewCloudResourceClient(tracer, conn, authCfg, o.GrpcClientAuthenticationAllowInsecure) + if err != nil { + return err + } + getter := apistore.NewRESTOptionsGetterForClient(unified, etcdOptions.StorageConfig) + serverConfig.RESTOptionsGetter = getter return nil } diff --git a/pkg/services/apiserver/options/storage_test.go b/pkg/services/apiserver/options/storage_test.go index 540dcac4fdc..658acd0bfb0 100644 --- a/pkg/services/apiserver/options/storage_test.go +++ b/pkg/services/apiserver/options/storage_test.go @@ -63,3 +63,40 @@ func TestStorageOptions_CheckFeatureToggle(t *testing.T) { }) } } + +func TestStorageOptions_Validate(t *testing.T) { + tests := []struct { + name string + Opts StorageOptions + wantErr bool + }{ + { + name: "with unified storage grpc and no auth token", + Opts: StorageOptions{ + StorageType: StorageTypeUnifiedGrpc, + }, + wantErr: true, + }, + { + name: "with unified storage grpc and auth info", + Opts: StorageOptions{ + StorageType: StorageTypeUnifiedGrpc, + Address: "localhost:10000", + GrpcClientAuthenticationToken: "1234", + GrpcClientAuthenticationTokenExchangeURL: "http://localhost:8080", + GrpcClientAuthenticationTokenNamespace: "*", + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + errs := tt.Opts.Validate() + if tt.wantErr { + assert.NotEmpty(t, errs) + return + } + assert.Empty(t, errs) + }) + } +}