* Chore: Unify token exchange rount trippers * Remove the conditional provider for now * Remove unecessary strategy * test cleanup * Lint
162 lines
5.0 KiB
Go
162 lines
5.0 KiB
Go
package authorizer
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"github.com/grafana/authlib/authn"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/client-go/dynamic"
|
|
"k8s.io/client-go/rest"
|
|
|
|
dashboardv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1"
|
|
folderv1 "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
|
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
|
"github.com/grafana/grafana/pkg/clientauth"
|
|
)
|
|
|
|
var (
|
|
ErrNoConfigProvider = errors.New("no config provider for group resource")
|
|
ErrNoVersionInfo = errors.New("no version info for group resource")
|
|
|
|
Versions = map[schema.GroupResource]string{
|
|
{Group: folderv1.GROUP, Resource: folderv1.RESOURCE}: folderv1.VERSION,
|
|
{Group: dashboardv1.GROUP, Resource: dashboardv1.DASHBOARD_RESOURCE}: dashboardv1.VERSION,
|
|
}
|
|
)
|
|
|
|
// ConfigProvider is a function that provides a rest.Config for a given context.
|
|
type ConfigProvider func(ctx context.Context) (*rest.Config, error)
|
|
|
|
// DynamicClientFactory is a function that creates a dynamic.Interface from a rest.Config.
|
|
// This can be overridden in tests.
|
|
type DynamicClientFactory func(config *rest.Config) (dynamic.Interface, error)
|
|
|
|
// ParentProvider implementation that fetches the parent folder information from remote API servers.
|
|
type ParentProviderImpl struct {
|
|
configProviders map[schema.GroupResource]ConfigProvider
|
|
versions map[schema.GroupResource]string
|
|
dynamicClientFactory DynamicClientFactory
|
|
|
|
// Cache of dynamic clients for each group resource
|
|
// This is used to avoid creating a new dynamic client for each request
|
|
// and to reuse the same client for the same group resource.
|
|
clients map[schema.GroupResource]dynamic.Interface
|
|
clientsMu sync.Mutex
|
|
}
|
|
|
|
// DialConfig holds the configuration for dialing a remote API server.
|
|
type DialConfig struct {
|
|
Host string
|
|
Insecure bool
|
|
CAFile string
|
|
Audience string
|
|
}
|
|
|
|
// NewLocalConfigProvider creates a map of ConfigProviders that return the same given config for local API servers.
|
|
func NewLocalConfigProvider(
|
|
configProvider ConfigProvider,
|
|
) map[schema.GroupResource]ConfigProvider {
|
|
return map[schema.GroupResource]ConfigProvider{
|
|
{Group: folderv1.GROUP, Resource: folderv1.RESOURCE}: configProvider,
|
|
{Group: dashboardv1.GROUP, Resource: dashboardv1.DASHBOARD_RESOURCE}: configProvider,
|
|
}
|
|
}
|
|
|
|
// NewRemoteConfigProvider creates a map of ConfigProviders for remote API servers based on the given DialConfig.
|
|
func NewRemoteConfigProvider(cfg map[schema.GroupResource]DialConfig, exchangeClient authn.TokenExchanger) map[schema.GroupResource]ConfigProvider {
|
|
configProviders := make(map[schema.GroupResource]ConfigProvider, len(cfg))
|
|
for gr, dialConfig := range cfg {
|
|
configProviders[gr] = func(ctx context.Context) (*rest.Config, error) {
|
|
return &rest.Config{
|
|
Host: dialConfig.Host,
|
|
WrapTransport: clientauth.NewStaticTokenExchangeTransportWrapper(exchangeClient, dialConfig.Audience, clientauth.WildcardNamespace),
|
|
TLSClientConfig: rest.TLSClientConfig{
|
|
Insecure: dialConfig.Insecure,
|
|
CAFile: dialConfig.CAFile,
|
|
},
|
|
QPS: 50,
|
|
Burst: 100,
|
|
}, nil
|
|
}
|
|
}
|
|
return configProviders
|
|
}
|
|
|
|
// NewApiParentProvider creates a new ParentProviderImpl with the given config providers and version info.
|
|
func NewApiParentProvider(
|
|
configProviders map[schema.GroupResource]ConfigProvider,
|
|
version map[schema.GroupResource]string,
|
|
) *ParentProviderImpl {
|
|
return &ParentProviderImpl{
|
|
configProviders: configProviders,
|
|
versions: version,
|
|
dynamicClientFactory: func(config *rest.Config) (dynamic.Interface, error) {
|
|
return dynamic.NewForConfig(config)
|
|
},
|
|
clients: make(map[schema.GroupResource]dynamic.Interface),
|
|
}
|
|
}
|
|
|
|
func (p *ParentProviderImpl) HasParent(gr schema.GroupResource) bool {
|
|
_, ok := p.configProviders[gr]
|
|
return ok
|
|
}
|
|
|
|
func (p *ParentProviderImpl) getClient(ctx context.Context, gr schema.GroupResource) (dynamic.Interface, error) {
|
|
p.clientsMu.Lock()
|
|
client, ok := p.clients[gr]
|
|
p.clientsMu.Unlock()
|
|
|
|
if ok {
|
|
return client, nil
|
|
}
|
|
|
|
provider, ok := p.configProviders[gr]
|
|
if !ok {
|
|
return nil, fmt.Errorf("%w: %s", ErrNoConfigProvider, gr.String())
|
|
}
|
|
restConfig, err := provider(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
client, err = p.dynamicClientFactory(restConfig)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
p.clientsMu.Lock()
|
|
p.clients[gr] = client
|
|
p.clientsMu.Unlock()
|
|
|
|
return client, nil
|
|
}
|
|
|
|
func (p *ParentProviderImpl) GetParent(ctx context.Context, gr schema.GroupResource, namespace, name string) (string, error) {
|
|
client, err := p.getClient(ctx, gr)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
version, ok := p.versions[gr]
|
|
if !ok {
|
|
return "", fmt.Errorf("%w: %s", ErrNoVersionInfo, gr.String())
|
|
}
|
|
resourceClient := client.Resource(schema.GroupVersionResource{
|
|
Group: gr.Group,
|
|
Resource: gr.Resource,
|
|
Version: version,
|
|
}).Namespace(namespace)
|
|
|
|
unstructObj, err := resourceClient.Get(ctx, name, metav1.GetOptions{})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return unstructObj.GetAnnotations()[utils.AnnoKeyFolder], nil
|
|
}
|