diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 234247bbdb3..d679c8486ee 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -189,4 +189,5 @@ export interface FeatureToggles { newDashboardSharingComponent?: boolean; notificationBanner?: boolean; dashboardRestore?: boolean; + datasourceProxyDisableRBAC?: boolean; } diff --git a/pkg/api/pluginproxy/ds_proxy.go b/pkg/api/pluginproxy/ds_proxy.go index 8037c79a123..8d9ccdbdbe1 100644 --- a/pkg/api/pluginproxy/ds_proxy.go +++ b/pkg/api/pluginproxy/ds_proxy.go @@ -19,6 +19,7 @@ import ( glog "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/services/accesscontrol" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/featuremgmt" @@ -304,8 +305,14 @@ func (proxy *DataSourceProxy) validateRequest() error { continue } - if route.ReqRole.IsValid() { - if !proxy.ctx.HasUserRole(route.ReqRole) { + if proxy.features.IsEnabled(proxy.ctx.Req.Context(), featuremgmt.FlagDatasourceProxyDisableRBAC) { + // TODO(aarongodin): following logic can be removed with FlagDatasourceProxyDisableRBAC as it is covered by + // proxy.hasAccessToRoute(..) + if route.ReqRole.IsValid() && !proxy.ctx.HasUserRole(route.ReqRole) { + return errors.New("plugin proxy route access denied") + } + } else { + if !proxy.hasAccessToRoute(route) { return errors.New("plugin proxy route access denied") } } @@ -330,6 +337,26 @@ func (proxy *DataSourceProxy) validateRequest() error { return nil } +func (proxy *DataSourceProxy) hasAccessToRoute(route *plugins.Route) bool { + ctxLogger := logger.FromContext(proxy.ctx.Req.Context()) + useRBAC := proxy.features.IsEnabled(proxy.ctx.Req.Context(), featuremgmt.FlagAccessControlOnCall) && route.ReqAction != "" + if useRBAC { + routeEval := accesscontrol.EvalPermission(route.ReqAction) + ok := routeEval.Evaluate(proxy.ctx.GetPermissions()) + if !ok { + ctxLogger.Debug("plugin route is covered by RBAC, user doesn't have access", "route", proxy.ctx.Req.URL.Path, "action", route.ReqAction, "path", route.Path, "method", route.Method) + } + return ok + } + if route.ReqRole.IsValid() { + if hasUserRole := proxy.ctx.HasUserRole(route.ReqRole); !hasUserRole { + ctxLogger.Debug("plugin route is covered by org role, user doesn't have access", "route", proxy.ctx.Req.URL.Path, "role", route.ReqRole, "path", route.Path, "method", route.Method) + return false + } + } + return true +} + func (proxy *DataSourceProxy) logRequest() { if !proxy.cfg.DataProxyLogging { return diff --git a/pkg/api/pluginproxy/pluginproxy.go b/pkg/api/pluginproxy/pluginproxy.go index c61a6a284b9..5a959d97ab6 100644 --- a/pkg/api/pluginproxy/pluginproxy.go +++ b/pkg/api/pluginproxy/pluginproxy.go @@ -122,7 +122,7 @@ func (proxy *PluginProxy) HandleRequest() { } func (proxy *PluginProxy) hasAccessToRoute(route *plugins.Route) bool { - useRBAC := proxy.features.IsEnabled(proxy.ctx.Req.Context(), featuremgmt.FlagAccessControlOnCall) && route.RequiresRBACAction() + useRBAC := proxy.features.IsEnabled(proxy.ctx.Req.Context(), featuremgmt.FlagAccessControlOnCall) && route.ReqAction != "" if useRBAC { hasAccess := ac.HasAccess(proxy.accessControl, proxy.ctx)(ac.EvalPermission(route.ReqAction)) if !hasAccess { diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go index c1324178af7..3e5c8ea61e9 100644 --- a/pkg/plugins/plugins.go +++ b/pkg/plugins/plugins.go @@ -207,10 +207,6 @@ type Route struct { Body json.RawMessage `json:"body"` } -func (r *Route) RequiresRBACAction() bool { - return r.ReqAction != "" -} - // Header describes an HTTP header that is forwarded with // the proxied request for a plugin route type Header struct { diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index b37ed35124f..7a44f5a3445 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1275,6 +1275,13 @@ var ( HideFromDocs: true, HideFromAdminPage: true, }, + { + Name: "datasourceProxyDisableRBAC", + Description: "Disables applying a plugin route's ReqAction field to authorization", + Stage: FeatureStageGeneralAvailability, + Owner: identityAccessTeam, + HideFromDocs: true, + }, } ) diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 92a8b6ab65f..c5414ce66fe 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -170,3 +170,4 @@ logsExploreTableDefaultVisualization,experimental,@grafana/observability-logs,fa newDashboardSharingComponent,experimental,@grafana/sharing-squad,false,false,true notificationBanner,experimental,@grafana/grafana-frontend-platform,false,false,false dashboardRestore,experimental,@grafana/grafana-frontend-platform,false,false,false +datasourceProxyDisableRBAC,GA,@grafana/identity-access-team,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 8b2c3614b9b..52c5d0b4587 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -690,4 +690,8 @@ const ( // FlagDashboardRestore // Enables deleted dashboard restore feature FlagDashboardRestore = "dashboardRestore" + + // FlagDatasourceProxyDisableRBAC + // Disables applying a plugin route's ReqAction field to authorization + FlagDatasourceProxyDisableRBAC = "datasourceProxyDisableRBAC" ) diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index e0d53927e66..0c2383d09c3 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -2222,6 +2222,19 @@ "hideFromAdminPage": true, "hideFromDocs": true } + }, + { + "metadata": { + "name": "datasourceProxyDisableRBAC", + "resourceVersion": "1715889033198", + "creationTimestamp": "2024-05-16T19:50:33Z" + }, + "spec": { + "description": "Disables applying a plugin route's ReqAction field to authorization", + "stage": "GA", + "codeowner": "@grafana/identity-access-team", + "hideFromDocs": true + } } ] } \ No newline at end of file