datasources: querier: single-tenant: explicit permission-check (#102189)
This commit is contained in:
@@ -2,7 +2,6 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -13,9 +12,11 @@ import (
|
|||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||||
query "github.com/grafana/grafana/pkg/apis/query/v0alpha1"
|
query "github.com/grafana/grafana/pkg/apis/query/v0alpha1"
|
||||||
"github.com/grafana/grafana/pkg/plugins"
|
"github.com/grafana/grafana/pkg/plugins"
|
||||||
"github.com/grafana/grafana/pkg/registry/apis/query/clientapi"
|
"github.com/grafana/grafana/pkg/registry/apis/query/clientapi"
|
||||||
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
"github.com/grafana/grafana/pkg/services/datasources"
|
"github.com/grafana/grafana/pkg/services/datasources"
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
||||||
@@ -26,6 +27,7 @@ import (
|
|||||||
type pluginClient struct {
|
type pluginClient struct {
|
||||||
pluginClient plugins.Client
|
pluginClient plugins.Client
|
||||||
pCtxProvider *plugincontext.Provider
|
pCtxProvider *plugincontext.Provider
|
||||||
|
ac accesscontrol.AccessControl
|
||||||
}
|
}
|
||||||
|
|
||||||
type pluginRegistry struct {
|
type pluginRegistry struct {
|
||||||
@@ -43,10 +45,11 @@ var _ clientapi.QueryDataClient = (*pluginClient)(nil)
|
|||||||
var _ query.DataSourceApiServerRegistry = (*pluginRegistry)(nil)
|
var _ query.DataSourceApiServerRegistry = (*pluginRegistry)(nil)
|
||||||
|
|
||||||
// NewQueryClientForPluginClient creates a client that delegates to the internal plugins.Client stack
|
// NewQueryClientForPluginClient creates a client that delegates to the internal plugins.Client stack
|
||||||
func NewQueryClientForPluginClient(p plugins.Client, ctx *plugincontext.Provider) clientapi.QueryDataClient {
|
func NewQueryClientForPluginClient(p plugins.Client, ctx *plugincontext.Provider, accessControl accesscontrol.AccessControl) clientapi.QueryDataClient {
|
||||||
return &pluginClient{
|
return &pluginClient{
|
||||||
pluginClient: p,
|
pluginClient: p,
|
||||||
pCtxProvider: ctx,
|
pCtxProvider: ctx,
|
||||||
|
ac: accessControl,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,6 +62,17 @@ func NewDataSourceRegistryFromStore(pluginStore pluginstore.Store,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *pluginClient) CanQueryDataSource(ctx context.Context, uid string) (bool, error) {
|
||||||
|
user, err := identity.GetRequester(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
requiredScope := "datasources:uid:" + uid
|
||||||
|
evaluate := accesscontrol.EvalPermission(datasources.ActionQuery, requiredScope)
|
||||||
|
return d.ac.Evaluate(ctx, user, evaluate)
|
||||||
|
}
|
||||||
|
|
||||||
// ExecuteQueryData implements QueryHelper.
|
// ExecuteQueryData implements QueryHelper.
|
||||||
func (d *pluginClient) QueryData(ctx context.Context, req data.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
func (d *pluginClient) QueryData(ctx context.Context, req data.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
||||||
queries, dsRef, err := data.ToDataSourceQueries(req)
|
queries, dsRef, err := data.ToDataSourceQueries(req)
|
||||||
@@ -69,17 +83,23 @@ func (d *pluginClient) QueryData(ctx context.Context, req data.QueryDataRequest)
|
|||||||
return nil, fmt.Errorf("expected single datasource request")
|
return nil, fmt.Errorf("expected single datasource request")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canQuery, err := d.CanQueryDataSource(ctx, dsRef.UID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !canQuery {
|
||||||
|
status := metav1.Status{
|
||||||
|
Status: metav1.StatusFailure,
|
||||||
|
Code: http.StatusForbidden,
|
||||||
|
Message: "Access denied to the data source",
|
||||||
|
}
|
||||||
|
return nil, &apierrors.StatusError{ErrStatus: status}
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: this depends on uid unique across datasources
|
// NOTE: this depends on uid unique across datasources
|
||||||
settings, err := d.pCtxProvider.GetDataSourceInstanceSettings(ctx, dsRef.UID)
|
settings, err := d.pCtxProvider.GetDataSourceInstanceSettings(ctx, dsRef.UID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, datasources.ErrDataSourceNotFound) {
|
|
||||||
status := metav1.Status{
|
|
||||||
Status: metav1.StatusFailure,
|
|
||||||
Code: http.StatusNotFound,
|
|
||||||
Message: "datasource not found",
|
|
||||||
}
|
|
||||||
return nil, &apierrors.StatusError{ErrStatus: status}
|
|
||||||
}
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ func RegisterAPIService(features featuremgmt.FeatureToggles,
|
|||||||
builder, err := NewQueryAPIBuilder(
|
builder, err := NewQueryAPIBuilder(
|
||||||
features,
|
features,
|
||||||
&CommonDataSourceClientSupplier{
|
&CommonDataSourceClientSupplier{
|
||||||
Client: client.NewQueryClientForPluginClient(pluginClient, pCtxProvider),
|
Client: client.NewQueryClientForPluginClient(pluginClient, pCtxProvider, accessControl),
|
||||||
},
|
},
|
||||||
ar,
|
ar,
|
||||||
client.NewDataSourceRegistryFromStore(pluginStore, dataSourcesService),
|
client.NewDataSourceRegistryFromStore(pluginStore, dataSourcesService),
|
||||||
|
|||||||
Reference in New Issue
Block a user