diff --git a/apps/provisioning/pkg/auth/round_tripper.go b/apps/provisioning/pkg/auth/round_tripper.go index 327999457ae..45e15db7a0f 100644 --- a/apps/provisioning/pkg/auth/round_tripper.go +++ b/apps/provisioning/pkg/auth/round_tripper.go @@ -6,7 +6,6 @@ import ( "net/http" "github.com/grafana/authlib/authn" - provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" utilnet "k8s.io/apimachinery/pkg/util/net" ) @@ -19,20 +18,22 @@ type tokenExchanger interface { type RoundTripper struct { client tokenExchanger transport http.RoundTripper + audience string } // NewRoundTripper constructs a RoundTripper that exchanges the provided token per request // and forwards the request to the provided base transport. -func NewRoundTripper(tokenExchangeClient tokenExchanger, base http.RoundTripper) *RoundTripper { +func NewRoundTripper(tokenExchangeClient tokenExchanger, base http.RoundTripper, audience string) *RoundTripper { return &RoundTripper{ client: tokenExchangeClient, transport: base, + audience: audience, } } func (t *RoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { tokenResponse, err := t.client.Exchange(req.Context(), authn.TokenExchangeRequest{ - Audiences: []string{provisioning.GROUP}, + Audiences: []string{t.audience}, Namespace: "*", }) if err != nil { diff --git a/apps/provisioning/pkg/auth/round_tripper_test.go b/apps/provisioning/pkg/auth/round_tripper_test.go index 7925c46f973..259126db7b9 100644 --- a/apps/provisioning/pkg/auth/round_tripper_test.go +++ b/apps/provisioning/pkg/auth/round_tripper_test.go @@ -34,7 +34,7 @@ func TestRoundTripper_SetsAccessTokenHeader(t *testing.T) { rr := httptest.NewRecorder() rr.WriteHeader(http.StatusOK) return rr.Result(), nil - })) + }), "example-audience") req, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "http://example", nil) resp, err := tr.RoundTrip(req) @@ -50,7 +50,7 @@ func TestRoundTripper_PropagatesExchangeError(t *testing.T) { tr := NewRoundTripper(&fakeExchanger{err: io.EOF}, roundTripperFunc(func(_ *http.Request) (*http.Response, error) { t.Fatal("transport should not be called on exchange error") return nil, nil - })) + }), "example-audience") req, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "http://example", nil) resp, err := tr.RoundTrip(req) diff --git a/pkg/operators/provisioning/config.go b/pkg/operators/provisioning/config.go index 3d7f88fdb15..5b3c22bab97 100644 --- a/pkg/operators/provisioning/config.go +++ b/pkg/operators/provisioning/config.go @@ -111,7 +111,7 @@ func setupFromConfig(cfg *setting.Cfg) (controllerCfg *provisioningControllerCon APIPath: "/apis", Host: provisioningServerURL, WrapTransport: transport.WrapperFunc(func(rt http.RoundTripper) http.RoundTripper { - return authrt.NewRoundTripper(tokenExchangeClient, rt) + return authrt.NewRoundTripper(tokenExchangeClient, rt, provisioning.GROUP) }), TLSClientConfig: tlsConfig, } @@ -153,19 +153,23 @@ func setupFromConfig(cfg *setting.Cfg) (controllerCfg *provisioningControllerCon return nil, fmt.Errorf("folders_server_url is required in [operator] section") } - apiServerURLs := []string{dashboardsServerURL, foldersServerURL, provisioningServerURL} - configProviders := make([]apiserver.RestConfigProvider, len(apiServerURLs)) + apiServerURLs := map[string]string{ + resources.DashboardResource.Group: dashboardsServerURL, + resources.FolderResource.Group: foldersServerURL, + provisioning.GROUP: provisioningServerURL, + } + configProviders := make(map[string]apiserver.RestConfigProvider) - for i, url := range apiServerURLs { + for group, url := range apiServerURLs { config := &rest.Config{ APIPath: "/apis", Host: url, WrapTransport: transport.WrapperFunc(func(rt http.RoundTripper) http.RoundTripper { - return authrt.NewRoundTripper(tokenExchangeClient, rt) + return authrt.NewRoundTripper(tokenExchangeClient, rt, group) }), TLSClientConfig: tlsConfig, } - configProviders[i] = NewDirectConfigProvider(config) + configProviders[group] = NewDirectConfigProvider(config) } clients := resources.NewClientFactoryForMultipleAPIServers(configProviders) diff --git a/pkg/registry/apis/provisioning/resources/client.go b/pkg/registry/apis/provisioning/resources/client.go index dbf5dd56465..d74d9d653ed 100644 --- a/pkg/registry/apis/provisioning/resources/client.go +++ b/pkg/registry/apis/provisioning/resources/client.go @@ -120,26 +120,39 @@ func NewClientFactory(configProvider apiserver.RestConfigProvider) ClientFactory } // NewClientFactoryForMultipleAPIServers creates a ClientFactory for multiple API servers -func NewClientFactoryForMultipleAPIServers(configProviders []apiserver.RestConfigProvider) ClientFactory { - clientFactories := make([]ClientFactory, len(configProviders)) +func NewClientFactoryForMultipleAPIServers(configProviders map[string]apiserver.RestConfigProvider) ClientFactory { + clientFactories := make(map[string]ClientFactory) - for i, configProvider := range configProviders { + for api, configProvider := range configProviders { clientFactory := NewClientFactory(configProvider) - clientFactories[i] = clientFactory + clientFactories[api] = clientFactory } return &multiClientFactory{clientFactories: clientFactories} } type multiClientFactory struct { - clientFactories []ClientFactory + clientFactories map[string]ClientFactory } func (m *multiClientFactory) Clients(ctx context.Context, namespace string) (ResourceClients, error) { - for _, clientFactory := range m.clientFactories { - return clientFactory.Clients(ctx, namespace) + clients := make(map[string]ResourceClients) + for group, clientFactory := range m.clientFactories { + c, err := clientFactory.Clients(ctx, namespace) + if err != nil { + return nil, err + } + + clients[group] = c } - return nil, fmt.Errorf("no client factories available") + if len(clients) == 0 { + return nil, fmt.Errorf("no client factories available") + } + + return &multiResourceClients{ + namespace: namespace, + resourceClientsByAPIGroup: clients, + }, nil } func (f *clientFactory) Clients(ctx context.Context, namespace string) (ResourceClients, error) { @@ -283,6 +296,52 @@ func (c *resourceClients) User(ctx context.Context) (dynamic.ResourceInterface, return v, err } +type multiResourceClients struct { + namespace string + mutex sync.Mutex + resourceClientsByAPIGroup map[string]ResourceClients +} + +// ForKind returns a client for a kind. +// If the kind has a version, it will be used. +// If the kind does not have a version, the preferred version will be used. +func (c *multiResourceClients) ForKind(ctx context.Context, gvk schema.GroupVersionKind) (dynamic.ResourceInterface, schema.GroupVersionResource, error) { + c.mutex.Lock() + defer c.mutex.Unlock() + + resourceClients, ok := c.resourceClientsByAPIGroup[gvk.Group] + if !ok { + return nil, schema.GroupVersionResource{}, fmt.Errorf("no clients provider for group %s", gvk.Group) + } + + return resourceClients.ForKind(ctx, gvk) +} + +// ForResource returns a client for a resource. +// If the resource has a version, it will be used. +// If the resource does not have a version, the preferred version will be used. +func (c *multiResourceClients) ForResource(ctx context.Context, gvr schema.GroupVersionResource) (dynamic.ResourceInterface, schema.GroupVersionKind, error) { + c.mutex.Lock() + defer c.mutex.Unlock() + + resourceClients, ok := c.resourceClientsByAPIGroup[gvr.Group] + if !ok { + return nil, schema.GroupVersionKind{}, fmt.Errorf("no clients provider for group %s", gvr.Group) + } + + return resourceClients.ForResource(ctx, gvr) +} + +func (c *multiResourceClients) Folder(ctx context.Context) (dynamic.ResourceInterface, error) { + client, _, err := c.ForResource(ctx, FolderResource) + return client, err +} + +func (c *multiResourceClients) User(ctx context.Context) (dynamic.ResourceInterface, error) { + v, _, err := c.ForResource(ctx, UserResource) + return v, err +} + // ForEach applies the function to each resource returned from the list operation func ForEach(ctx context.Context, client dynamic.ResourceInterface, fn func(item *unstructured.Unstructured) error) error { var continueToken string