folder: allow standalone folder apiserver (#99508)

* add standalone folders

* add validate

* fix auth
This commit is contained in:
Georges Chaudy 2025-01-27 15:50:06 +01:00 committed by GitHub
parent a5916116b1
commit 7720f0b64e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 119 additions and 7 deletions

View File

@ -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)

View File

@ -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
}

View File

@ -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)
})
}
}