mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
RBAC: Check plugins:install
globally (#78438)
* RBAC: Check plugins:install globally * Add disclamer to the RBACSingleOrganization config option
This commit is contained in:
parent
9e11779921
commit
b6b86bb0b3
@ -83,7 +83,7 @@ The following tables list permissions associated with basic and fixed roles.
|
||||
| `fixed:organization:reader` | `orgs:read`<br>`orgs.quotas:read` | Read an organization and its quotas. |
|
||||
| `fixed:organization:writer` | All permissions from `fixed:organization:reader` and <br> `orgs:write`<br>`orgs.preferences:read`<br>`orgs.preferences:write` | Read an organization, its quotas, or its preferences. Update organization properties, or its preferences. |
|
||||
| `fixed:plugins.app:reader` | `plugins.app:access` | Access application plugins (still enforcing the organization role). |
|
||||
| `fixed:plugins:maintainer` | `plugins:install` | Install and uninstall plugins. |
|
||||
| `fixed:plugins:maintainer` | `plugins:install` | Install and uninstall plugins. Needs to be assigned globally. |
|
||||
| `fixed:plugins:writer` | `plugins:write` | Enable and disable plugins and edit plugins' settings. |
|
||||
| `fixed:provisioning:writer` | `provisioning:reload` | Reload provisioning. |
|
||||
| `fixed:reports:reader` | `reports:read`<br>`reports:send`<br>`reports.settings:read` | Read all reports and shared report settings. |
|
||||
|
@ -394,8 +394,8 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
|
||||
if hs.Cfg.PluginAdminEnabled && (hs.Features.IsEnabledGlobally(featuremgmt.FlagManagedPluginsInstall) || !hs.Cfg.PluginAdminExternalManageEnabled) {
|
||||
apiRoute.Group("/plugins", func(pluginRoute routing.RouteRegister) {
|
||||
pluginRoute.Post("/:pluginId/install", authorize(ac.EvalPermission(pluginaccesscontrol.ActionInstall)), routing.Wrap(hs.InstallPlugin))
|
||||
pluginRoute.Post("/:pluginId/uninstall", authorize(ac.EvalPermission(pluginaccesscontrol.ActionInstall)), routing.Wrap(hs.UninstallPlugin))
|
||||
pluginRoute.Post("/:pluginId/install", authorizeInOrg(ac.UseGlobalOrSingleOrg(hs.Cfg), ac.EvalPermission(pluginaccesscontrol.ActionInstall)), routing.Wrap(hs.InstallPlugin))
|
||||
pluginRoute.Post("/:pluginId/uninstall", authorizeInOrg(ac.UseGlobalOrSingleOrg(hs.Cfg), ac.EvalPermission(pluginaccesscontrol.ActionInstall)), routing.Wrap(hs.UninstallPlugin))
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/registry"
|
||||
"github.com/grafana/grafana/pkg/plugins/pluginscdn"
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/org/orgtest"
|
||||
@ -43,23 +44,31 @@ func Test_PluginsInstallAndUninstall(t *testing.T) {
|
||||
canInstall := []ac.Permission{{Action: pluginaccesscontrol.ActionInstall}}
|
||||
cannotInstall := []ac.Permission{{Action: "plugins:cannotinstall"}}
|
||||
|
||||
localOrg := int64(1)
|
||||
globalOrg := int64(ac.GlobalOrgID)
|
||||
|
||||
type testCase struct {
|
||||
expectedCode int
|
||||
permissionOrg int64
|
||||
permissions []ac.Permission
|
||||
pluginAdminEnabled bool
|
||||
pluginAdminExternalManageEnabled bool
|
||||
singleOrganization bool
|
||||
}
|
||||
tcs := []testCase{
|
||||
{expectedCode: http.StatusNotFound, permissions: canInstall, pluginAdminEnabled: true, pluginAdminExternalManageEnabled: true},
|
||||
{expectedCode: http.StatusNotFound, permissions: canInstall, pluginAdminEnabled: false, pluginAdminExternalManageEnabled: true},
|
||||
{expectedCode: http.StatusNotFound, permissions: canInstall, pluginAdminEnabled: false, pluginAdminExternalManageEnabled: false},
|
||||
{expectedCode: http.StatusForbidden, permissions: cannotInstall, pluginAdminEnabled: true, pluginAdminExternalManageEnabled: false},
|
||||
{expectedCode: http.StatusOK, permissions: canInstall, pluginAdminEnabled: true, pluginAdminExternalManageEnabled: false},
|
||||
{expectedCode: http.StatusNotFound, permissionOrg: globalOrg, permissions: canInstall, pluginAdminEnabled: true, pluginAdminExternalManageEnabled: true},
|
||||
{expectedCode: http.StatusNotFound, permissionOrg: globalOrg, permissions: canInstall, pluginAdminEnabled: false, pluginAdminExternalManageEnabled: true},
|
||||
{expectedCode: http.StatusNotFound, permissionOrg: globalOrg, permissions: canInstall, pluginAdminEnabled: false, pluginAdminExternalManageEnabled: false},
|
||||
{expectedCode: http.StatusForbidden, permissionOrg: globalOrg, permissions: cannotInstall, pluginAdminEnabled: true, pluginAdminExternalManageEnabled: false},
|
||||
{expectedCode: http.StatusOK, permissionOrg: globalOrg, permissions: canInstall, pluginAdminEnabled: true, pluginAdminExternalManageEnabled: false},
|
||||
{expectedCode: http.StatusForbidden, permissionOrg: localOrg, permissions: canInstall, pluginAdminEnabled: true, pluginAdminExternalManageEnabled: false},
|
||||
{expectedCode: http.StatusForbidden, permissionOrg: localOrg, permissions: cannotInstall, pluginAdminEnabled: true, pluginAdminExternalManageEnabled: false, singleOrganization: true},
|
||||
{expectedCode: http.StatusOK, permissionOrg: localOrg, permissions: canInstall, pluginAdminEnabled: true, pluginAdminExternalManageEnabled: false, singleOrganization: true},
|
||||
}
|
||||
|
||||
testName := func(action string, tc testCase) string {
|
||||
return fmt.Sprintf("%s request returns %d when adminEnabled: %t, externalEnabled: %t, permissions: %q",
|
||||
action, tc.expectedCode, tc.pluginAdminEnabled, tc.pluginAdminExternalManageEnabled, tc.permissions)
|
||||
return fmt.Sprintf("%s request returns %d when adminEnabled: %t, externalEnabled: %t, permissions: %d: %s, single_org: %t",
|
||||
action, tc.expectedCode, tc.pluginAdminEnabled, tc.pluginAdminExternalManageEnabled, tc.permissionOrg, tc.permissions[0].Action, tc.singleOrganization)
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
@ -67,15 +76,18 @@ func Test_PluginsInstallAndUninstall(t *testing.T) {
|
||||
hs.Cfg = setting.NewCfg()
|
||||
hs.Cfg.PluginAdminEnabled = tc.pluginAdminEnabled
|
||||
hs.Cfg.PluginAdminExternalManageEnabled = tc.pluginAdminExternalManageEnabled
|
||||
hs.Cfg.RBACSingleOrganization = tc.singleOrganization
|
||||
|
||||
hs.orgService = &orgtest.FakeOrgService{ExpectedOrg: &org.Org{}}
|
||||
hs.accesscontrolService = &actest.FakeService{}
|
||||
|
||||
hs.pluginInstaller = NewFakePluginInstaller()
|
||||
hs.pluginFileStore = &fakes.FakePluginFileStore{}
|
||||
})
|
||||
|
||||
t.Run(testName("Install", tc), func(t *testing.T) {
|
||||
input := strings.NewReader(`{"version": "1.0.2"}`)
|
||||
req := webtest.RequestWithSignedInUser(server.NewPostRequest("/api/plugins/test/install", input), userWithPermissions(1, tc.permissions))
|
||||
req := webtest.RequestWithSignedInUser(server.NewPostRequest("/api/plugins/test/install", input), userWithPermissions(tc.permissionOrg, tc.permissions))
|
||||
res, err := server.SendJSON(req)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expectedCode, res.StatusCode)
|
||||
@ -84,7 +96,7 @@ func Test_PluginsInstallAndUninstall(t *testing.T) {
|
||||
|
||||
t.Run(testName("Uninstall", tc), func(t *testing.T) {
|
||||
input := strings.NewReader("{ }")
|
||||
req := webtest.RequestWithSignedInUser(server.NewPostRequest("/api/plugins/test/uninstall", input), userWithPermissions(1, tc.permissions))
|
||||
req := webtest.RequestWithSignedInUser(server.NewPostRequest("/api/plugins/test/uninstall", input), userWithPermissions(tc.permissionOrg, tc.permissions))
|
||||
res, err := server.SendJSON(req)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expectedCode, res.StatusCode)
|
||||
|
@ -252,6 +252,16 @@ func UseGlobalOrg(c *contextmodel.ReqContext) (int64, error) {
|
||||
return GlobalOrgID, nil
|
||||
}
|
||||
|
||||
// UseGlobalOrSingleOrg returns the global organization or the current organization in a single organization setup
|
||||
func UseGlobalOrSingleOrg(cfg *setting.Cfg) OrgIDGetter {
|
||||
return func(c *contextmodel.ReqContext) (int64, error) {
|
||||
if cfg.RBACSingleOrganization {
|
||||
return c.GetOrgID(), nil
|
||||
}
|
||||
return GlobalOrgID, nil
|
||||
}
|
||||
}
|
||||
|
||||
// scopeParams holds the parameters used to fill in scope templates
|
||||
type scopeParams struct {
|
||||
OrgID int64
|
||||
|
@ -59,7 +59,7 @@ func DeclareRBACRoles(service ac.Service, cfg *setting.Cfg) error {
|
||||
Role: ac.RoleDTO{
|
||||
Name: ac.FixedRolePrefix + "plugins:maintainer",
|
||||
DisplayName: "Plugin Maintainer",
|
||||
Description: "Install, uninstall plugins",
|
||||
Description: "Install, uninstall plugins. Needs to be assigned globally.",
|
||||
Group: "Plugins",
|
||||
Permissions: []ac.Permission{
|
||||
{Action: ActionInstall},
|
||||
|
@ -548,6 +548,8 @@ type Cfg struct {
|
||||
RBACPermissionValidationEnabled bool
|
||||
// Reset basic roles permissions on start-up
|
||||
RBACResetBasicRoles bool
|
||||
// RBAC single organization. This configuration option is subject to change.
|
||||
RBACSingleOrganization bool
|
||||
|
||||
// GRPC Server.
|
||||
GRPCServerNetwork string
|
||||
@ -1694,6 +1696,7 @@ func readAccessControlSettings(iniFile *ini.File, cfg *Cfg) {
|
||||
cfg.RBACPermissionCache = rbac.Key("permission_cache").MustBool(true)
|
||||
cfg.RBACPermissionValidationEnabled = rbac.Key("permission_validation_enabled").MustBool(false)
|
||||
cfg.RBACResetBasicRoles = rbac.Key("reset_basic_roles").MustBool(false)
|
||||
cfg.RBACSingleOrganization = rbac.Key("single_organization").MustBool(false)
|
||||
}
|
||||
|
||||
func readOAuth2ServerSettings(cfg *Cfg) {
|
||||
|
Loading…
Reference in New Issue
Block a user