mirror of
https://github.com/grafana/grafana.git
synced 2025-01-19 13:03:32 -06:00
RBAC: Allow app plugins access restriction (#51524)
* RBAC: Allow app plugins restriction Co-authored-by: Kalle Persson <kalle.persson@grafana.com> * Fix tests * Imports * WIP * Adding RBAC to AppPluginsRoutes * Switching middleware order * Restrict access to resources * Nit * Cosmetic changes * Fix fallback * Moving declaration to HttpServer Co-Authored-By: marefr <marcus.efraimsson@gmail.com> Co-authored-by: Kalle Persson <kalle.persson@grafana.com> Co-authored-by: marefr <marcus.efraimsson@gmail.com>
This commit is contained in:
parent
0c33b9f211
commit
5975c4bc6d
@ -2,6 +2,7 @@ package api
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
@ -36,6 +37,11 @@ var (
|
||||
// grants to organization roles ("Viewer", "Editor", "Admin") or "Grafana Admin"
|
||||
// that HTTPServer needs
|
||||
func (hs *HTTPServer) declareFixedRoles() error {
|
||||
// Declare plugins roles
|
||||
if err := plugins.DeclareRBACRoles(hs.AccessControl); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
provisioningWriterRole := ac.RoleRegistration{
|
||||
Role: ac.RoleDTO{
|
||||
Name: "fixed:provisioning:writer",
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
@ -88,8 +89,10 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
r.Get("/plugins/:id/", reqSignedIn, hs.Index)
|
||||
r.Get("/plugins/:id/edit", reqSignedIn, hs.Index) // deprecated
|
||||
r.Get("/plugins/:id/page/:page", reqSignedIn, hs.Index)
|
||||
r.Get("/a/:id/*", reqSignedIn, hs.Index) // App Root Page
|
||||
r.Get("/a/:id", reqSignedIn, hs.Index)
|
||||
// App Root Page
|
||||
appPluginIDScope := plugins.ScopeProvider.GetResourceScope(":id")
|
||||
r.Get("/a/:id/*", authorize(reqSignedIn, ac.EvalPermission(plugins.ActionAppAccess, appPluginIDScope)), hs.Index)
|
||||
r.Get("/a/:id", authorize(reqSignedIn, ac.EvalPermission(plugins.ActionAppAccess, appPluginIDScope)), hs.Index)
|
||||
|
||||
r.Get("/d/:uid/:slug", reqSignedIn, redirectFromLegacyPanelEditURL, hs.Index)
|
||||
r.Get("/d/:uid", reqSignedIn, redirectFromLegacyPanelEditURL, hs.Index)
|
||||
@ -325,12 +328,13 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
datasourceRoute.Get("/id/:name", authorize(reqSignedIn, ac.EvalPermission(datasources.ActionIDRead, nameScope)), routing.Wrap(hs.GetDataSourceIdByName))
|
||||
})
|
||||
|
||||
pluginIDScope := plugins.ScopeProvider.GetResourceScope(":pluginId")
|
||||
apiRoute.Get("/plugins", routing.Wrap(hs.GetPluginList))
|
||||
apiRoute.Get("/plugins/:pluginId/settings", routing.Wrap(hs.GetPluginSettingByID))
|
||||
apiRoute.Get("/plugins/:pluginId/settings", routing.Wrap(hs.GetPluginSettingByID)) // RBAC check performed in handler for App Plugins
|
||||
apiRoute.Get("/plugins/:pluginId/markdown/:name", routing.Wrap(hs.GetPluginMarkdown))
|
||||
apiRoute.Get("/plugins/:pluginId/health", routing.Wrap(hs.CheckHealth))
|
||||
apiRoute.Any("/plugins/:pluginId/resources", hs.CallResource)
|
||||
apiRoute.Any("/plugins/:pluginId/resources/*", hs.CallResource)
|
||||
apiRoute.Any("/plugins/:pluginId/resources", authorize(reqSignedIn, ac.EvalPermission(plugins.ActionAppAccess, pluginIDScope)), hs.CallResource)
|
||||
apiRoute.Any("/plugins/:pluginId/resources/*", authorize(reqSignedIn, ac.EvalPermission(plugins.ActionAppAccess, pluginIDScope)), hs.CallResource)
|
||||
apiRoute.Get("/plugins/errors", routing.Wrap(hs.GetPluginErrorsList))
|
||||
|
||||
if hs.Cfg.PluginAdminEnabled && !hs.Cfg.PluginAdminExternalManageEnabled {
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
)
|
||||
@ -42,6 +43,11 @@ func (hs *HTTPServer) initAppPluginRoutes(r *web.Mux) {
|
||||
ReqSignedIn: true,
|
||||
}))
|
||||
|
||||
// Preventing access to plugin routes if the user has no right to access the plugin
|
||||
authorize := ac.Middleware(hs.AccessControl)
|
||||
handlers = append(handlers, authorize(middleware.ReqSignedIn,
|
||||
ac.EvalPermission(plugins.ActionAppAccess, plugins.ScopeProvider.GetResourceScope(plugin.ID))))
|
||||
|
||||
if route.ReqRole != "" {
|
||||
if route.ReqRole == models.ROLE_ADMIN {
|
||||
handlers = append(handlers, middleware.RoleAuth(models.ROLE_ADMIN))
|
||||
@ -49,6 +55,7 @@ func (hs *HTTPServer) initAppPluginRoutes(r *web.Mux) {
|
||||
handlers = append(handlers, middleware.RoleAuth(models.ROLE_EDITOR, models.ROLE_ADMIN))
|
||||
}
|
||||
}
|
||||
|
||||
handlers = append(handlers, AppPluginRoute(route, plugin.ID, hs))
|
||||
for _, method := range strings.Split(route.Method, ",") {
|
||||
r.Handle(strings.TrimSpace(method), url, handlers)
|
||||
|
@ -76,6 +76,7 @@ func (hs *HTTPServer) getProfileNode(c *models.ReqContext) *dtos.NavLink {
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) getAppLinks(c *models.ReqContext) ([]*dtos.NavLink, error) {
|
||||
hasAccess := ac.HasAccess(hs.AccessControl, c)
|
||||
enabledPlugins, err := hs.enabledPlugins(c.Req.Context(), c.OrgId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -87,6 +88,11 @@ func (hs *HTTPServer) getAppLinks(c *models.ReqContext) ([]*dtos.NavLink, error)
|
||||
continue
|
||||
}
|
||||
|
||||
if !hasAccess(ac.ReqSignedIn,
|
||||
ac.EvalPermission(plugins.ActionAppAccess, plugins.ScopeProvider.GetResourceScope(plugin.ID))) {
|
||||
continue
|
||||
}
|
||||
|
||||
appLink := &dtos.NavLink{
|
||||
Text: plugin.Name,
|
||||
Id: "plugin-page-" + plugin.ID,
|
||||
|
@ -119,7 +119,17 @@ func (hs *HTTPServer) GetPluginSettingByID(c *models.ReqContext) response.Respon
|
||||
|
||||
plugin, exists := hs.pluginStore.Plugin(c.Req.Context(), pluginID)
|
||||
if !exists {
|
||||
return response.Error(404, "Plugin not found, no installed plugin with that id", nil)
|
||||
return response.Error(http.StatusNotFound, "Plugin not found, no installed plugin with that id", nil)
|
||||
}
|
||||
|
||||
// In a first iteration, we only have one permission for app plugins.
|
||||
// We will need a different permission to allow users to configure the plugin without needing access to it.
|
||||
if plugin.IsApp() {
|
||||
hasAccess := accesscontrol.HasAccess(hs.AccessControl, c)
|
||||
if !hasAccess(accesscontrol.ReqSignedIn,
|
||||
accesscontrol.EvalPermission(plugins.ActionAppAccess, plugins.ScopeProvider.GetResourceScope(plugin.ID))) {
|
||||
return response.Error(http.StatusForbidden, "Access Denied", nil)
|
||||
}
|
||||
}
|
||||
|
||||
dto := &dtos.PluginSetting{
|
||||
|
30
pkg/plugins/accesscontrol.go
Normal file
30
pkg/plugins/accesscontrol.go
Normal file
@ -0,0 +1,30 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
)
|
||||
|
||||
const (
|
||||
ActionAppAccess = "plugins.app:access"
|
||||
)
|
||||
|
||||
var (
|
||||
ScopeProvider = ac.NewScopeProvider("plugins")
|
||||
)
|
||||
|
||||
func DeclareRBACRoles(acService ac.AccessControl) error {
|
||||
AppPluginsReader := ac.RoleRegistration{
|
||||
Role: ac.RoleDTO{
|
||||
Name: ac.FixedRolePrefix + "plugins.app:reader",
|
||||
DisplayName: "Application Plugins Access",
|
||||
Description: "Access application plugins (still enforcing the organization role)",
|
||||
Group: "Plugins",
|
||||
Permissions: []ac.Permission{
|
||||
{Action: ActionAppAccess, Scope: ScopeProvider.GetResourceAllScope()},
|
||||
},
|
||||
},
|
||||
Grants: []string{string(models.ROLE_VIEWER)},
|
||||
}
|
||||
return acService.DeclareFixedRoles(AppPluginsReader)
|
||||
}
|
@ -7,6 +7,11 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"gopkg.in/ini.v1"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
@ -35,12 +40,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/tsdb/prometheus"
|
||||
"github.com/grafana/grafana/pkg/tsdb/tempo"
|
||||
"github.com/grafana/grafana/pkg/tsdb/testdatasource"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
func TestPluginManager_int_init(t *testing.T) {
|
||||
|
@ -7,12 +7,11 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-azure-sdk-go/azsettings"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana-azure-sdk-go/azsettings"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin"
|
||||
|
Loading…
Reference in New Issue
Block a user