mirror of
https://github.com/grafana/grafana.git
synced 2025-02-16 18:34:52 -06:00
RBAC: Allow omitting default permissions when a new resource is created (#90720)
* Cfg: Move rbac settings to own struct * Cfg: Add setting to control if resource should generate managed permissions when created * Dashboards: Check if we should generate default permissions when dashboard is created * Folders: Check if we should generate default permissions when folder is created * Datasource: Check if we should generate default permissions when datasource is created * ServiceAccount: Check if we should generate default permissions when service account is created * Cfg: Add option to specify resources for wich we should default seed * ManagedPermissions: Move providers to their own files * Dashboards: Default seed all possible managed permissions if configured * Folders: Default seed all possible managed permissions if configured * Cfg: Remove service account from list * RBAC: Move utility function * remove managed permission settings from the config file examples, change the setting names * remove ini file changes from the PR * fix setting reading * fix linting errors * fix tests * fix wildcard role seeding --------- Co-authored-by: Karl Persson <kalle.persson@grafana.com> Co-authored-by: jguer <me@jguer.space>
This commit is contained in:
parent
82236976ae
commit
9bb2cf4968
@ -188,6 +188,9 @@ func (hs *HTTPServer) CreateFolder(c *contextmodel.ReqContext) response.Response
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) setDefaultFolderPermissions(ctx context.Context, orgID int64, user identity.Requester, folder *folder.Folder) error {
|
||||
if !hs.Cfg.RBAC.PermissionsOnCreation("folder") {
|
||||
return nil
|
||||
}
|
||||
var permissions []accesscontrol.SetResourcePermissionCommand
|
||||
var userID int64
|
||||
|
||||
|
@ -550,7 +550,7 @@ func (hs *HTTPServer) hasPluginRequestedPermissions(c *contextmodel.ReqContext,
|
||||
hs.log.Debug("check installer's permissions, plugin wants to register an external service")
|
||||
evaluator := evalAllPermissions(plugin.JSONData.IAM.Permissions)
|
||||
hasAccess := ac.HasGlobalAccess(hs.AccessControl, hs.authnService, c)
|
||||
if hs.Cfg.RBACSingleOrganization {
|
||||
if hs.Cfg.RBAC.SingleOrganization {
|
||||
// In a single organization setup, no need for a global check
|
||||
hasAccess = ac.HasAccess(hs.AccessControl, c)
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ 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.Cfg.RBAC.SingleOrganization = tc.singleOrganization
|
||||
|
||||
hs.orgService = &orgtest.FakeOrgService{ExpectedOrg: &org.Org{}}
|
||||
hs.accesscontrolService = &actest.FakeService{}
|
||||
@ -743,7 +743,7 @@ func TestHTTPServer_hasPluginRequestedPermissions(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
hs.Cfg = setting.NewCfg()
|
||||
hs.Cfg.RBACSingleOrganization = tt.singleOrg
|
||||
hs.Cfg.RBAC.SingleOrganization = tt.singleOrg
|
||||
hs.pluginStore = &pluginstore.FakePluginStore{
|
||||
PluginList: []pluginstore.Plugin{tt.plugin},
|
||||
}
|
||||
|
@ -362,6 +362,20 @@ func GetOrgRoles(user identity.Requester) []string {
|
||||
return roles
|
||||
}
|
||||
|
||||
// PermissionsForActions generate Permissions for all actions provided scoped to provided scope.
|
||||
func PermissionsForActions(actions []string, scope string) []Permission {
|
||||
permissions := make([]Permission, len(actions))
|
||||
|
||||
for i, action := range actions {
|
||||
permissions[i] = Permission{
|
||||
Action: action,
|
||||
Scope: scope,
|
||||
}
|
||||
}
|
||||
|
||||
return permissions
|
||||
}
|
||||
|
||||
func BackgroundUser(name string, orgID int64, role org.RoleType, permissions []Permission) identity.Requester {
|
||||
return &user.SignedInUser{
|
||||
OrgID: orgID,
|
||||
|
@ -119,7 +119,7 @@ func (s *Service) GetUserPermissions(ctx context.Context, user identity.Requeste
|
||||
timer := prometheus.NewTimer(metrics.MAccessPermissionsSummary)
|
||||
defer timer.ObserveDuration()
|
||||
|
||||
if !s.cfg.RBACPermissionCache || !user.HasUniqueId() {
|
||||
if !s.cfg.RBAC.PermissionCache || !user.HasUniqueId() {
|
||||
return s.getUserPermissions(ctx, user, options)
|
||||
}
|
||||
|
||||
|
@ -233,7 +233,7 @@ func UseGlobalOrg(c *contextmodel.ReqContext) (int64, error) {
|
||||
// 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 {
|
||||
if cfg.RBAC.SingleOrganization {
|
||||
return c.GetOrgID(), nil
|
||||
}
|
||||
return GlobalOrgID, nil
|
||||
@ -271,7 +271,7 @@ func UseGlobalOrgFromRequestData(cfg *setting.Cfg) OrgIDGetter {
|
||||
|
||||
// We only check permissions in the global organization if we are not running a SingleOrganization setup
|
||||
// That allows Organization Admins to modify global roles and make global assignments.
|
||||
if query.Global && !cfg.RBACSingleOrganization {
|
||||
if query.Global && !cfg.RBAC.SingleOrganization {
|
||||
return GlobalOrgID, nil
|
||||
}
|
||||
|
||||
@ -284,7 +284,7 @@ func UseGlobalOrgFromRequestParams(cfg *setting.Cfg) OrgIDGetter {
|
||||
return func(c *contextmodel.ReqContext) (int64, error) {
|
||||
// We only check permissions in the global organization if we are not running a SingleOrganization setup
|
||||
// That allows Organization Admins to modify global roles and make global assignments, and is intended for use in hosted Grafana.
|
||||
if c.QueryBool("global") && !cfg.RBACSingleOrganization {
|
||||
if c.QueryBool("global") && !cfg.RBAC.SingleOrganization {
|
||||
return GlobalOrgID, nil
|
||||
}
|
||||
|
||||
|
177
pkg/services/accesscontrol/ossaccesscontrol/dashboard.go
Normal file
177
pkg/services/accesscontrol/ossaccesscontrol/dashboard.go
Normal file
@ -0,0 +1,177 @@
|
||||
package ossaccesscontrol
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/infra/metrics"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/licensing"
|
||||
"github.com/grafana/grafana/pkg/services/team"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
type DashboardPermissionsService struct {
|
||||
*resourcepermissions.Service
|
||||
}
|
||||
|
||||
var DashboardViewActions = []string{dashboards.ActionDashboardsRead}
|
||||
var DashboardEditActions = append(DashboardViewActions, []string{dashboards.ActionDashboardsWrite, dashboards.ActionDashboardsDelete}...)
|
||||
var DashboardAdminActions = append(DashboardEditActions, []string{dashboards.ActionDashboardsPermissionsRead, dashboards.ActionDashboardsPermissionsWrite}...)
|
||||
|
||||
func getDashboardViewActions(features featuremgmt.FeatureToggles) []string {
|
||||
if features.IsEnabled(context.Background(), featuremgmt.FlagAnnotationPermissionUpdate) {
|
||||
return append(DashboardViewActions, accesscontrol.ActionAnnotationsRead)
|
||||
}
|
||||
return DashboardViewActions
|
||||
}
|
||||
|
||||
func getDashboardEditActions(features featuremgmt.FeatureToggles) []string {
|
||||
if features.IsEnabled(context.Background(), featuremgmt.FlagAnnotationPermissionUpdate) {
|
||||
return append(DashboardEditActions, []string{accesscontrol.ActionAnnotationsRead, accesscontrol.ActionAnnotationsWrite, accesscontrol.ActionAnnotationsDelete, accesscontrol.ActionAnnotationsCreate}...)
|
||||
}
|
||||
return DashboardEditActions
|
||||
}
|
||||
|
||||
func getDashboardAdminActions(features featuremgmt.FeatureToggles) []string {
|
||||
if features.IsEnabled(context.Background(), featuremgmt.FlagAnnotationPermissionUpdate) {
|
||||
return append(DashboardAdminActions, []string{accesscontrol.ActionAnnotationsRead, accesscontrol.ActionAnnotationsWrite, accesscontrol.ActionAnnotationsDelete, accesscontrol.ActionAnnotationsCreate}...)
|
||||
}
|
||||
return DashboardAdminActions
|
||||
}
|
||||
|
||||
func registerDashboardRoles(cfg *setting.Cfg, features featuremgmt.FeatureToggles, service accesscontrol.Service) error {
|
||||
if !cfg.RBAC.PermissionsWildcardSeed("dashboard") {
|
||||
return nil
|
||||
}
|
||||
|
||||
viewer := accesscontrol.RoleRegistration{
|
||||
Role: accesscontrol.RoleDTO{
|
||||
Name: "fixed:dashboards:viewer",
|
||||
DisplayName: "Viewer",
|
||||
Description: "View all dashboards",
|
||||
Group: "Dashboards",
|
||||
Permissions: accesscontrol.PermissionsForActions(getDashboardViewActions(features), dashboards.ScopeDashboardsAll),
|
||||
Hidden: true,
|
||||
},
|
||||
Grants: []string{"Viewer"},
|
||||
}
|
||||
|
||||
editor := accesscontrol.RoleRegistration{
|
||||
Role: accesscontrol.RoleDTO{
|
||||
Name: "fixed:dashboards:editor",
|
||||
DisplayName: "Editor",
|
||||
Description: "Edit all dashboards.",
|
||||
Group: "Dashboards",
|
||||
Permissions: accesscontrol.PermissionsForActions(getDashboardEditActions(features), dashboards.ScopeDashboardsAll),
|
||||
Hidden: true,
|
||||
},
|
||||
Grants: []string{"Editor"},
|
||||
}
|
||||
|
||||
admin := accesscontrol.RoleRegistration{
|
||||
Role: accesscontrol.RoleDTO{
|
||||
Name: "fixed:dashboards:admin",
|
||||
DisplayName: "Admin",
|
||||
Description: "Administer all dashboards.",
|
||||
Group: "Dashboards",
|
||||
Permissions: accesscontrol.PermissionsForActions(getDashboardAdminActions(features), dashboards.ScopeDashboardsAll),
|
||||
Hidden: true,
|
||||
},
|
||||
Grants: []string{"Admin"},
|
||||
}
|
||||
|
||||
return service.DeclareFixedRoles(viewer, editor, admin)
|
||||
}
|
||||
|
||||
func ProvideDashboardPermissions(
|
||||
cfg *setting.Cfg, features featuremgmt.FeatureToggles, router routing.RouteRegister, sql db.DB, ac accesscontrol.AccessControl,
|
||||
license licensing.Licensing, dashboardStore dashboards.Store, folderService folder.Service, service accesscontrol.Service,
|
||||
teamService team.Service, userService user.Service, actionSetService resourcepermissions.ActionSetService,
|
||||
) (*DashboardPermissionsService, error) {
|
||||
getDashboard := func(ctx context.Context, orgID int64, resourceID string) (*dashboards.Dashboard, error) {
|
||||
query := &dashboards.GetDashboardQuery{UID: resourceID, OrgID: orgID}
|
||||
queryResult, err := dashboardStore.GetDashboard(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return queryResult, nil
|
||||
}
|
||||
|
||||
if err := registerDashboardRoles(cfg, features, service); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
options := resourcepermissions.Options{
|
||||
Resource: "dashboards",
|
||||
ResourceAttribute: "uid",
|
||||
ResourceValidator: func(ctx context.Context, orgID int64, resourceID string) error {
|
||||
dashboard, err := getDashboard(ctx, orgID, resourceID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if dashboard.IsFolder {
|
||||
return errors.New("not found")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
InheritedScopesSolver: func(ctx context.Context, orgID int64, resourceID string) ([]string, error) {
|
||||
wildcards := accesscontrol.WildcardsFromPrefix(dashboards.ScopeFoldersPrefix)
|
||||
scopes := []string(wildcards)
|
||||
|
||||
dashboard, err := getDashboard(ctx, orgID, resourceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
metrics.MFolderIDsServiceCount.WithLabelValues(metrics.AccessControl).Inc()
|
||||
// nolint:staticcheck
|
||||
if dashboard.FolderUID != "" {
|
||||
query := &dashboards.GetDashboardQuery{UID: dashboard.FolderUID, OrgID: orgID}
|
||||
queryResult, err := dashboardStore.GetDashboard(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parentScope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(queryResult.UID)
|
||||
|
||||
nestedScopes, err := dashboards.GetInheritedScopes(ctx, orgID, queryResult.UID, folderService)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
scopes = append(scopes, parentScope)
|
||||
scopes = append(scopes, nestedScopes...)
|
||||
return scopes, nil
|
||||
}
|
||||
return append(scopes, dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.GeneralFolderUID)), nil
|
||||
},
|
||||
Assignments: resourcepermissions.Assignments{
|
||||
Users: true,
|
||||
Teams: true,
|
||||
BuiltInRoles: true,
|
||||
ServiceAccounts: true,
|
||||
},
|
||||
PermissionsToActions: map[string][]string{
|
||||
"View": getDashboardViewActions(features),
|
||||
"Edit": getDashboardEditActions(features),
|
||||
"Admin": getDashboardAdminActions(features),
|
||||
},
|
||||
ReaderRoleName: "Dashboard permission reader",
|
||||
WriterRoleName: "Dashboard permission writer",
|
||||
RoleGroup: "Dashboards",
|
||||
}
|
||||
|
||||
srv, err := resourcepermissions.New(cfg, options, features, router, license, ac, service, sql, teamService, userService, actionSetService)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &DashboardPermissionsService{srv}, nil
|
||||
}
|
87
pkg/services/accesscontrol/ossaccesscontrol/datasource.go
Normal file
87
pkg/services/accesscontrol/ossaccesscontrol/datasource.go
Normal file
@ -0,0 +1,87 @@
|
||||
package ossaccesscontrol
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
// DatasourceQueryActions contains permissions to read information
|
||||
// about a data source and submit arbitrary queries to it.
|
||||
var DatasourceQueryActions = []string{
|
||||
datasources.ActionRead,
|
||||
datasources.ActionQuery,
|
||||
}
|
||||
|
||||
func ProvideDatasourcePermissionsService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, db db.DB) *DatasourcePermissionsService {
|
||||
return &DatasourcePermissionsService{
|
||||
store: resourcepermissions.NewStore(cfg, db, features),
|
||||
}
|
||||
}
|
||||
|
||||
var _ accesscontrol.DatasourcePermissionsService = new(DatasourcePermissionsService)
|
||||
|
||||
type DatasourcePermissionsService struct {
|
||||
store resourcepermissions.Store
|
||||
}
|
||||
|
||||
func (e DatasourcePermissionsService) GetPermissions(ctx context.Context, user identity.Requester, resourceID string) ([]accesscontrol.ResourcePermission, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (e DatasourcePermissionsService) SetUserPermission(ctx context.Context, orgID int64, user accesscontrol.User, resourceID, permission string) (*accesscontrol.ResourcePermission, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (e DatasourcePermissionsService) SetTeamPermission(ctx context.Context, orgID, teamID int64, resourceID, permission string) (*accesscontrol.ResourcePermission, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (e DatasourcePermissionsService) SetBuiltInRolePermission(ctx context.Context, orgID int64, builtInRole string, resourceID string, permission string) (*accesscontrol.ResourcePermission, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// SetPermissions sets managed permissions for a datasource in OSS. This ensures that Viewers and Editors maintain query access to a data source
|
||||
// if an OSS/unlicensed instance is upgraded to Enterprise/licensed.
|
||||
// https://github.com/grafana/identity-access-team/issues/672
|
||||
func (e DatasourcePermissionsService) SetPermissions(ctx context.Context, orgID int64, resourceID string, commands ...accesscontrol.SetResourcePermissionCommand) ([]accesscontrol.ResourcePermission, error) {
|
||||
dbCommands := make([]resourcepermissions.SetResourcePermissionsCommand, 0, len(commands))
|
||||
for _, cmd := range commands {
|
||||
// Only set query permissions for built-in roles; do not set permissions for data sources with * as UID, as this would grant wildcard permissions
|
||||
if cmd.Permission != "Query" || cmd.BuiltinRole == "" || resourceID == "*" {
|
||||
continue
|
||||
}
|
||||
actions := DatasourceQueryActions
|
||||
|
||||
dbCommands = append(dbCommands, resourcepermissions.SetResourcePermissionsCommand{
|
||||
BuiltinRole: cmd.BuiltinRole,
|
||||
SetResourcePermissionCommand: resourcepermissions.SetResourcePermissionCommand{
|
||||
Actions: actions,
|
||||
Resource: datasources.ScopeRoot,
|
||||
ResourceID: resourceID,
|
||||
ResourceAttribute: "uid",
|
||||
Permission: cmd.Permission,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return e.store.SetResourcePermissions(ctx, orgID, dbCommands, resourcepermissions.ResourceHooks{})
|
||||
}
|
||||
|
||||
func (e DatasourcePermissionsService) DeleteResourcePermissions(ctx context.Context, orgID int64, resourceID string) error {
|
||||
return e.store.DeleteResourcePermissions(ctx, orgID, &resourcepermissions.DeleteResourcePermissionsCmd{
|
||||
Resource: datasources.ScopeRoot,
|
||||
ResourceAttribute: "uid",
|
||||
ResourceID: resourceID,
|
||||
})
|
||||
}
|
||||
|
||||
func (e DatasourcePermissionsService) MapActions(permission accesscontrol.ResourcePermission) string {
|
||||
return ""
|
||||
}
|
133
pkg/services/accesscontrol/ossaccesscontrol/folder.go
Normal file
133
pkg/services/accesscontrol/ossaccesscontrol/folder.go
Normal file
@ -0,0 +1,133 @@
|
||||
package ossaccesscontrol
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/libraryelements"
|
||||
"github.com/grafana/grafana/pkg/services/licensing"
|
||||
"github.com/grafana/grafana/pkg/services/team"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
type FolderPermissionsService struct {
|
||||
*resourcepermissions.Service
|
||||
}
|
||||
|
||||
var FolderViewActions = []string{dashboards.ActionFoldersRead, accesscontrol.ActionAlertingRuleRead, libraryelements.ActionLibraryPanelsRead, accesscontrol.ActionAlertingSilencesRead}
|
||||
var FolderEditActions = append(FolderViewActions, []string{
|
||||
dashboards.ActionFoldersWrite,
|
||||
dashboards.ActionFoldersDelete,
|
||||
dashboards.ActionDashboardsCreate,
|
||||
accesscontrol.ActionAlertingRuleCreate,
|
||||
accesscontrol.ActionAlertingRuleUpdate,
|
||||
accesscontrol.ActionAlertingRuleDelete,
|
||||
accesscontrol.ActionAlertingSilencesCreate,
|
||||
accesscontrol.ActionAlertingSilencesWrite,
|
||||
libraryelements.ActionLibraryPanelsCreate,
|
||||
libraryelements.ActionLibraryPanelsWrite,
|
||||
libraryelements.ActionLibraryPanelsDelete,
|
||||
}...)
|
||||
var FolderAdminActions = append(FolderEditActions, []string{dashboards.ActionFoldersPermissionsRead, dashboards.ActionFoldersPermissionsWrite}...)
|
||||
|
||||
func registerFolderRoles(cfg *setting.Cfg, features featuremgmt.FeatureToggles, service accesscontrol.Service) error {
|
||||
if !cfg.RBAC.PermissionsWildcardSeed("folder") {
|
||||
return nil
|
||||
}
|
||||
|
||||
viewer := accesscontrol.RoleRegistration{
|
||||
Role: accesscontrol.RoleDTO{
|
||||
Name: "fixed:folders:viewer",
|
||||
DisplayName: "Viewer",
|
||||
Description: "View all folders and dashboards.",
|
||||
Group: "Folders",
|
||||
Permissions: accesscontrol.PermissionsForActions(append(getDashboardViewActions(features), FolderViewActions...), dashboards.ScopeFoldersAll),
|
||||
Hidden: true,
|
||||
},
|
||||
Grants: []string{"Viewer"},
|
||||
}
|
||||
|
||||
editor := accesscontrol.RoleRegistration{
|
||||
Role: accesscontrol.RoleDTO{
|
||||
Name: "fixed:folders:editor",
|
||||
DisplayName: "Editor",
|
||||
Description: "Edit all folders and dashboards.",
|
||||
Group: "Folders",
|
||||
Permissions: accesscontrol.PermissionsForActions(append(getDashboardEditActions(features), FolderEditActions...), dashboards.ScopeFoldersAll),
|
||||
Hidden: true,
|
||||
},
|
||||
Grants: []string{"Editor"},
|
||||
}
|
||||
|
||||
admin := accesscontrol.RoleRegistration{
|
||||
Role: accesscontrol.RoleDTO{
|
||||
Name: "fixed:folders:admin",
|
||||
DisplayName: "Admin",
|
||||
Description: "Administer all folders and dashboards",
|
||||
Group: "folders",
|
||||
Permissions: accesscontrol.PermissionsForActions(append(getDashboardAdminActions(features), FolderAdminActions...), dashboards.ScopeFoldersAll),
|
||||
Hidden: true,
|
||||
},
|
||||
Grants: []string{"Admin"},
|
||||
}
|
||||
|
||||
return service.DeclareFixedRoles(viewer, editor, admin)
|
||||
}
|
||||
|
||||
func ProvideFolderPermissions(
|
||||
cfg *setting.Cfg, features featuremgmt.FeatureToggles, router routing.RouteRegister, sql db.DB, accesscontrol accesscontrol.AccessControl,
|
||||
license licensing.Licensing, dashboardStore dashboards.Store, folderService folder.Service, service accesscontrol.Service,
|
||||
teamService team.Service, userService user.Service, actionSetService resourcepermissions.ActionSetService,
|
||||
) (*FolderPermissionsService, error) {
|
||||
if err := registerFolderRoles(cfg, features, service); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
options := resourcepermissions.Options{
|
||||
Resource: "folders",
|
||||
ResourceAttribute: "uid",
|
||||
ResourceValidator: func(ctx context.Context, orgID int64, resourceID string) error {
|
||||
query := &dashboards.GetDashboardQuery{UID: resourceID, OrgID: orgID}
|
||||
queryResult, err := dashboardStore.GetDashboard(ctx, query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !queryResult.IsFolder {
|
||||
return errors.New("not found")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
InheritedScopesSolver: func(ctx context.Context, orgID int64, resourceID string) ([]string, error) {
|
||||
return dashboards.GetInheritedScopes(ctx, orgID, resourceID, folderService)
|
||||
},
|
||||
Assignments: resourcepermissions.Assignments{
|
||||
Users: true,
|
||||
Teams: true,
|
||||
BuiltInRoles: true,
|
||||
ServiceAccounts: true,
|
||||
},
|
||||
PermissionsToActions: map[string][]string{
|
||||
"View": append(getDashboardViewActions(features), FolderViewActions...),
|
||||
"Edit": append(getDashboardEditActions(features), FolderEditActions...),
|
||||
"Admin": append(getDashboardAdminActions(features), FolderAdminActions...),
|
||||
},
|
||||
ReaderRoleName: "Folder permission reader",
|
||||
WriterRoleName: "Folder permission writer",
|
||||
RoleGroup: "Folders",
|
||||
}
|
||||
srv, err := resourcepermissions.New(cfg, options, features, router, license, accesscontrol, service, sql, teamService, userService, actionSetService)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &FolderPermissionsService{srv}, nil
|
||||
}
|
@ -1,417 +0,0 @@
|
||||
package ossaccesscontrol
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/infra/metrics"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/libraryelements"
|
||||
"github.com/grafana/grafana/pkg/services/licensing"
|
||||
"github.com/grafana/grafana/pkg/services/serviceaccounts"
|
||||
"github.com/grafana/grafana/pkg/services/serviceaccounts/retriever"
|
||||
"github.com/grafana/grafana/pkg/services/team"
|
||||
"github.com/grafana/grafana/pkg/services/team/teamimpl"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
type TeamPermissionsService struct {
|
||||
*resourcepermissions.Service
|
||||
}
|
||||
|
||||
var (
|
||||
TeamMemberActions = []string{
|
||||
accesscontrol.ActionTeamsRead,
|
||||
}
|
||||
|
||||
TeamAdminActions = []string{
|
||||
accesscontrol.ActionTeamsRead,
|
||||
accesscontrol.ActionTeamsDelete,
|
||||
accesscontrol.ActionTeamsWrite,
|
||||
accesscontrol.ActionTeamsPermissionsRead,
|
||||
accesscontrol.ActionTeamsPermissionsWrite,
|
||||
}
|
||||
)
|
||||
|
||||
func ProvideTeamPermissions(
|
||||
cfg *setting.Cfg, features featuremgmt.FeatureToggles, router routing.RouteRegister, sql db.DB,
|
||||
ac accesscontrol.AccessControl, license licensing.Licensing, service accesscontrol.Service,
|
||||
teamService team.Service, userService user.Service, actionSetService resourcepermissions.ActionSetService,
|
||||
) (*TeamPermissionsService, error) {
|
||||
options := resourcepermissions.Options{
|
||||
Resource: "teams",
|
||||
ResourceAttribute: "id",
|
||||
OnlyManaged: true,
|
||||
ResourceValidator: func(ctx context.Context, orgID int64, resourceID string) error {
|
||||
id, err := strconv.ParseInt(resourceID, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = teamService.GetTeamByID(context.Background(), &team.GetTeamByIDQuery{
|
||||
OrgID: orgID,
|
||||
ID: id,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
Assignments: resourcepermissions.Assignments{
|
||||
Users: true,
|
||||
Teams: false,
|
||||
BuiltInRoles: false,
|
||||
},
|
||||
PermissionsToActions: map[string][]string{
|
||||
"Member": TeamMemberActions,
|
||||
"Admin": TeamAdminActions,
|
||||
},
|
||||
ReaderRoleName: "Team permission reader",
|
||||
WriterRoleName: "Team permission writer",
|
||||
RoleGroup: "Teams",
|
||||
OnSetUser: func(session *db.Session, orgID int64, user accesscontrol.User, resourceID, permission string) error {
|
||||
teamId, err := strconv.ParseInt(resourceID, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch permission {
|
||||
case "Member":
|
||||
return teamimpl.AddOrUpdateTeamMemberHook(session, user.ID, orgID, teamId, user.IsExternal, 0)
|
||||
case "Admin":
|
||||
return teamimpl.AddOrUpdateTeamMemberHook(session, user.ID, orgID, teamId, user.IsExternal, dashboardaccess.PERMISSION_ADMIN)
|
||||
case "":
|
||||
return teamimpl.RemoveTeamMemberHook(session, &team.RemoveTeamMemberCommand{
|
||||
OrgID: orgID,
|
||||
UserID: user.ID,
|
||||
TeamID: teamId,
|
||||
})
|
||||
default:
|
||||
return fmt.Errorf("invalid team permission type %s", permission)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
srv, err := resourcepermissions.New(cfg, options, features, router, license, ac, service, sql, teamService, userService, actionSetService)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &TeamPermissionsService{srv}, nil
|
||||
}
|
||||
|
||||
type DashboardPermissionsService struct {
|
||||
*resourcepermissions.Service
|
||||
}
|
||||
|
||||
var DashboardViewActions = []string{dashboards.ActionDashboardsRead}
|
||||
var DashboardEditActions = append(DashboardViewActions, []string{dashboards.ActionDashboardsWrite, dashboards.ActionDashboardsDelete}...)
|
||||
var DashboardAdminActions = append(DashboardEditActions, []string{dashboards.ActionDashboardsPermissionsRead, dashboards.ActionDashboardsPermissionsWrite}...)
|
||||
|
||||
func getDashboardViewActions(features featuremgmt.FeatureToggles) []string {
|
||||
if features.IsEnabled(context.Background(), featuremgmt.FlagAnnotationPermissionUpdate) {
|
||||
return append(DashboardViewActions, accesscontrol.ActionAnnotationsRead)
|
||||
}
|
||||
return DashboardViewActions
|
||||
}
|
||||
|
||||
func getDashboardEditActions(features featuremgmt.FeatureToggles) []string {
|
||||
if features.IsEnabled(context.Background(), featuremgmt.FlagAnnotationPermissionUpdate) {
|
||||
return append(DashboardEditActions, []string{accesscontrol.ActionAnnotationsRead, accesscontrol.ActionAnnotationsWrite, accesscontrol.ActionAnnotationsDelete, accesscontrol.ActionAnnotationsCreate}...)
|
||||
}
|
||||
return DashboardEditActions
|
||||
}
|
||||
|
||||
func getDashboardAdminActions(features featuremgmt.FeatureToggles) []string {
|
||||
if features.IsEnabled(context.Background(), featuremgmt.FlagAnnotationPermissionUpdate) {
|
||||
return append(DashboardAdminActions, []string{accesscontrol.ActionAnnotationsRead, accesscontrol.ActionAnnotationsWrite, accesscontrol.ActionAnnotationsDelete, accesscontrol.ActionAnnotationsCreate}...)
|
||||
}
|
||||
return DashboardAdminActions
|
||||
}
|
||||
|
||||
func ProvideDashboardPermissions(
|
||||
cfg *setting.Cfg, features featuremgmt.FeatureToggles, router routing.RouteRegister, sql db.DB, ac accesscontrol.AccessControl,
|
||||
license licensing.Licensing, dashboardStore dashboards.Store, folderService folder.Service, service accesscontrol.Service,
|
||||
teamService team.Service, userService user.Service, actionSetService resourcepermissions.ActionSetService,
|
||||
) (*DashboardPermissionsService, error) {
|
||||
getDashboard := func(ctx context.Context, orgID int64, resourceID string) (*dashboards.Dashboard, error) {
|
||||
query := &dashboards.GetDashboardQuery{UID: resourceID, OrgID: orgID}
|
||||
queryResult, err := dashboardStore.GetDashboard(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return queryResult, nil
|
||||
}
|
||||
|
||||
options := resourcepermissions.Options{
|
||||
Resource: "dashboards",
|
||||
ResourceAttribute: "uid",
|
||||
ResourceValidator: func(ctx context.Context, orgID int64, resourceID string) error {
|
||||
dashboard, err := getDashboard(ctx, orgID, resourceID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if dashboard.IsFolder {
|
||||
return errors.New("not found")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
InheritedScopesSolver: func(ctx context.Context, orgID int64, resourceID string) ([]string, error) {
|
||||
wildcards := accesscontrol.WildcardsFromPrefix(dashboards.ScopeFoldersPrefix)
|
||||
scopes := []string(wildcards)
|
||||
|
||||
dashboard, err := getDashboard(ctx, orgID, resourceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
metrics.MFolderIDsServiceCount.WithLabelValues(metrics.AccessControl).Inc()
|
||||
// nolint:staticcheck
|
||||
if dashboard.FolderUID != "" {
|
||||
query := &dashboards.GetDashboardQuery{UID: dashboard.FolderUID, OrgID: orgID}
|
||||
queryResult, err := dashboardStore.GetDashboard(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parentScope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(queryResult.UID)
|
||||
|
||||
nestedScopes, err := dashboards.GetInheritedScopes(ctx, orgID, queryResult.UID, folderService)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
scopes = append(scopes, parentScope)
|
||||
scopes = append(scopes, nestedScopes...)
|
||||
return scopes, nil
|
||||
}
|
||||
return append(scopes, dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.GeneralFolderUID)), nil
|
||||
},
|
||||
Assignments: resourcepermissions.Assignments{
|
||||
Users: true,
|
||||
Teams: true,
|
||||
BuiltInRoles: true,
|
||||
ServiceAccounts: true,
|
||||
},
|
||||
PermissionsToActions: map[string][]string{
|
||||
"View": getDashboardViewActions(features),
|
||||
"Edit": getDashboardEditActions(features),
|
||||
"Admin": getDashboardAdminActions(features),
|
||||
},
|
||||
ReaderRoleName: "Dashboard permission reader",
|
||||
WriterRoleName: "Dashboard permission writer",
|
||||
RoleGroup: "Dashboards",
|
||||
}
|
||||
|
||||
srv, err := resourcepermissions.New(cfg, options, features, router, license, ac, service, sql, teamService, userService, actionSetService)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &DashboardPermissionsService{srv}, nil
|
||||
}
|
||||
|
||||
type FolderPermissionsService struct {
|
||||
*resourcepermissions.Service
|
||||
}
|
||||
|
||||
var FolderViewActions = []string{dashboards.ActionFoldersRead, accesscontrol.ActionAlertingRuleRead, libraryelements.ActionLibraryPanelsRead, accesscontrol.ActionAlertingSilencesRead}
|
||||
var FolderEditActions = append(FolderViewActions, []string{
|
||||
dashboards.ActionFoldersWrite,
|
||||
dashboards.ActionFoldersDelete,
|
||||
dashboards.ActionDashboardsCreate,
|
||||
accesscontrol.ActionAlertingRuleCreate,
|
||||
accesscontrol.ActionAlertingRuleUpdate,
|
||||
accesscontrol.ActionAlertingRuleDelete,
|
||||
accesscontrol.ActionAlertingSilencesCreate,
|
||||
accesscontrol.ActionAlertingSilencesWrite,
|
||||
libraryelements.ActionLibraryPanelsCreate,
|
||||
libraryelements.ActionLibraryPanelsWrite,
|
||||
libraryelements.ActionLibraryPanelsDelete,
|
||||
}...)
|
||||
var FolderAdminActions = append(FolderEditActions, []string{dashboards.ActionFoldersPermissionsRead, dashboards.ActionFoldersPermissionsWrite}...)
|
||||
|
||||
func ProvideFolderPermissions(
|
||||
cfg *setting.Cfg, features featuremgmt.FeatureToggles, router routing.RouteRegister, sql db.DB, accesscontrol accesscontrol.AccessControl,
|
||||
license licensing.Licensing, dashboardStore dashboards.Store, folderService folder.Service, service accesscontrol.Service,
|
||||
teamService team.Service, userService user.Service, actionSetService resourcepermissions.ActionSetService,
|
||||
) (*FolderPermissionsService, error) {
|
||||
options := resourcepermissions.Options{
|
||||
Resource: "folders",
|
||||
ResourceAttribute: "uid",
|
||||
ResourceValidator: func(ctx context.Context, orgID int64, resourceID string) error {
|
||||
query := &dashboards.GetDashboardQuery{UID: resourceID, OrgID: orgID}
|
||||
queryResult, err := dashboardStore.GetDashboard(ctx, query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !queryResult.IsFolder {
|
||||
return errors.New("not found")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
InheritedScopesSolver: func(ctx context.Context, orgID int64, resourceID string) ([]string, error) {
|
||||
return dashboards.GetInheritedScopes(ctx, orgID, resourceID, folderService)
|
||||
},
|
||||
Assignments: resourcepermissions.Assignments{
|
||||
Users: true,
|
||||
Teams: true,
|
||||
BuiltInRoles: true,
|
||||
ServiceAccounts: true,
|
||||
},
|
||||
PermissionsToActions: map[string][]string{
|
||||
"View": append(getDashboardViewActions(features), FolderViewActions...),
|
||||
"Edit": append(getDashboardEditActions(features), FolderEditActions...),
|
||||
"Admin": append(getDashboardAdminActions(features), FolderAdminActions...),
|
||||
},
|
||||
ReaderRoleName: "Folder permission reader",
|
||||
WriterRoleName: "Folder permission writer",
|
||||
RoleGroup: "Folders",
|
||||
}
|
||||
srv, err := resourcepermissions.New(cfg, options, features, router, license, accesscontrol, service, sql, teamService, userService, actionSetService)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &FolderPermissionsService{srv}, nil
|
||||
}
|
||||
|
||||
// DatasourceQueryActions contains permissions to read information
|
||||
// about a data source and submit arbitrary queries to it.
|
||||
var DatasourceQueryActions = []string{
|
||||
datasources.ActionRead,
|
||||
datasources.ActionQuery,
|
||||
}
|
||||
|
||||
func ProvideDatasourcePermissionsService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, db db.DB) *DatasourcePermissionsService {
|
||||
return &DatasourcePermissionsService{
|
||||
store: resourcepermissions.NewStore(cfg, db, features),
|
||||
}
|
||||
}
|
||||
|
||||
var _ accesscontrol.DatasourcePermissionsService = new(DatasourcePermissionsService)
|
||||
|
||||
type DatasourcePermissionsService struct {
|
||||
store resourcepermissions.Store
|
||||
}
|
||||
|
||||
func (e DatasourcePermissionsService) GetPermissions(ctx context.Context, user identity.Requester, resourceID string) ([]accesscontrol.ResourcePermission, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (e DatasourcePermissionsService) SetUserPermission(ctx context.Context, orgID int64, user accesscontrol.User, resourceID, permission string) (*accesscontrol.ResourcePermission, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (e DatasourcePermissionsService) SetTeamPermission(ctx context.Context, orgID, teamID int64, resourceID, permission string) (*accesscontrol.ResourcePermission, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (e DatasourcePermissionsService) SetBuiltInRolePermission(ctx context.Context, orgID int64, builtInRole string, resourceID string, permission string) (*accesscontrol.ResourcePermission, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// SetPermissions sets managed permissions for a datasource in OSS. This ensures that Viewers and Editors maintain query access to a data source
|
||||
// if an OSS/unlicensed instance is upgraded to Enterprise/licensed.
|
||||
// https://github.com/grafana/identity-access-team/issues/672
|
||||
func (e DatasourcePermissionsService) SetPermissions(ctx context.Context, orgID int64, resourceID string, commands ...accesscontrol.SetResourcePermissionCommand) ([]accesscontrol.ResourcePermission, error) {
|
||||
dbCommands := make([]resourcepermissions.SetResourcePermissionsCommand, 0, len(commands))
|
||||
for _, cmd := range commands {
|
||||
// Only set query permissions for built-in roles; do not set permissions for data sources with * as UID, as this would grant wildcard permissions
|
||||
if cmd.Permission != "Query" || cmd.BuiltinRole == "" || resourceID == "*" {
|
||||
continue
|
||||
}
|
||||
actions := DatasourceQueryActions
|
||||
|
||||
dbCommands = append(dbCommands, resourcepermissions.SetResourcePermissionsCommand{
|
||||
BuiltinRole: cmd.BuiltinRole,
|
||||
SetResourcePermissionCommand: resourcepermissions.SetResourcePermissionCommand{
|
||||
Actions: actions,
|
||||
Resource: datasources.ScopeRoot,
|
||||
ResourceID: resourceID,
|
||||
ResourceAttribute: "uid",
|
||||
Permission: cmd.Permission,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return e.store.SetResourcePermissions(ctx, orgID, dbCommands, resourcepermissions.ResourceHooks{})
|
||||
}
|
||||
|
||||
func (e DatasourcePermissionsService) DeleteResourcePermissions(ctx context.Context, orgID int64, resourceID string) error {
|
||||
return e.store.DeleteResourcePermissions(ctx, orgID, &resourcepermissions.DeleteResourcePermissionsCmd{
|
||||
Resource: datasources.ScopeRoot,
|
||||
ResourceAttribute: "uid",
|
||||
ResourceID: resourceID,
|
||||
})
|
||||
}
|
||||
|
||||
func (e DatasourcePermissionsService) MapActions(permission accesscontrol.ResourcePermission) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
var (
|
||||
ServiceAccountEditActions = []string{
|
||||
serviceaccounts.ActionRead,
|
||||
serviceaccounts.ActionWrite,
|
||||
}
|
||||
ServiceAccountAdminActions = []string{
|
||||
serviceaccounts.ActionRead,
|
||||
serviceaccounts.ActionWrite,
|
||||
serviceaccounts.ActionDelete,
|
||||
serviceaccounts.ActionPermissionsRead,
|
||||
serviceaccounts.ActionPermissionsWrite,
|
||||
}
|
||||
)
|
||||
|
||||
type ServiceAccountPermissionsService struct {
|
||||
*resourcepermissions.Service
|
||||
}
|
||||
|
||||
func ProvideServiceAccountPermissions(
|
||||
cfg *setting.Cfg, features featuremgmt.FeatureToggles, router routing.RouteRegister, sql db.DB, ac accesscontrol.AccessControl,
|
||||
license licensing.Licensing, serviceAccountRetrieverService *retriever.Service, service accesscontrol.Service,
|
||||
teamService team.Service, userService user.Service, actionSetService resourcepermissions.ActionSetService,
|
||||
) (*ServiceAccountPermissionsService, error) {
|
||||
options := resourcepermissions.Options{
|
||||
Resource: "serviceaccounts",
|
||||
ResourceAttribute: "id",
|
||||
ResourceValidator: func(ctx context.Context, orgID int64, resourceID string) error {
|
||||
id, err := strconv.ParseInt(resourceID, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = serviceAccountRetrieverService.RetrieveServiceAccount(ctx, orgID, id)
|
||||
return err
|
||||
},
|
||||
Assignments: resourcepermissions.Assignments{
|
||||
Users: true,
|
||||
Teams: true,
|
||||
BuiltInRoles: false,
|
||||
},
|
||||
PermissionsToActions: map[string][]string{
|
||||
"Edit": ServiceAccountEditActions,
|
||||
"Admin": ServiceAccountAdminActions,
|
||||
},
|
||||
ReaderRoleName: "Service account permission reader",
|
||||
WriterRoleName: "Service account permission writer",
|
||||
RoleGroup: "Service accounts",
|
||||
}
|
||||
|
||||
srv, err := resourcepermissions.New(cfg, options, features, router, license, ac, service, sql, teamService, userService, actionSetService)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ServiceAccountPermissionsService{srv}, nil
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
package ossaccesscontrol
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/licensing"
|
||||
"github.com/grafana/grafana/pkg/services/serviceaccounts"
|
||||
"github.com/grafana/grafana/pkg/services/serviceaccounts/retriever"
|
||||
"github.com/grafana/grafana/pkg/services/team"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
var (
|
||||
ServiceAccountEditActions = []string{
|
||||
serviceaccounts.ActionRead,
|
||||
serviceaccounts.ActionWrite,
|
||||
}
|
||||
ServiceAccountAdminActions = []string{
|
||||
serviceaccounts.ActionRead,
|
||||
serviceaccounts.ActionWrite,
|
||||
serviceaccounts.ActionDelete,
|
||||
serviceaccounts.ActionPermissionsRead,
|
||||
serviceaccounts.ActionPermissionsWrite,
|
||||
}
|
||||
)
|
||||
|
||||
type ServiceAccountPermissionsService struct {
|
||||
*resourcepermissions.Service
|
||||
}
|
||||
|
||||
func ProvideServiceAccountPermissions(
|
||||
cfg *setting.Cfg, features featuremgmt.FeatureToggles, router routing.RouteRegister, sql db.DB, ac accesscontrol.AccessControl,
|
||||
license licensing.Licensing, serviceAccountRetrieverService *retriever.Service, service accesscontrol.Service,
|
||||
teamService team.Service, userService user.Service, actionSetService resourcepermissions.ActionSetService,
|
||||
) (*ServiceAccountPermissionsService, error) {
|
||||
options := resourcepermissions.Options{
|
||||
Resource: "serviceaccounts",
|
||||
ResourceAttribute: "id",
|
||||
ResourceValidator: func(ctx context.Context, orgID int64, resourceID string) error {
|
||||
id, err := strconv.ParseInt(resourceID, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = serviceAccountRetrieverService.RetrieveServiceAccount(ctx, orgID, id)
|
||||
return err
|
||||
},
|
||||
Assignments: resourcepermissions.Assignments{
|
||||
Users: true,
|
||||
Teams: true,
|
||||
BuiltInRoles: false,
|
||||
},
|
||||
PermissionsToActions: map[string][]string{
|
||||
"Edit": ServiceAccountEditActions,
|
||||
"Admin": ServiceAccountAdminActions,
|
||||
},
|
||||
ReaderRoleName: "Service account permission reader",
|
||||
WriterRoleName: "Service account permission writer",
|
||||
RoleGroup: "Service accounts",
|
||||
}
|
||||
|
||||
srv, err := resourcepermissions.New(cfg, options, features, router, license, ac, service, sql, teamService, userService, actionSetService)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ServiceAccountPermissionsService{srv}, nil
|
||||
}
|
103
pkg/services/accesscontrol/ossaccesscontrol/team.go
Normal file
103
pkg/services/accesscontrol/ossaccesscontrol/team.go
Normal file
@ -0,0 +1,103 @@
|
||||
package ossaccesscontrol
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/licensing"
|
||||
"github.com/grafana/grafana/pkg/services/team"
|
||||
"github.com/grafana/grafana/pkg/services/team/teamimpl"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
type TeamPermissionsService struct {
|
||||
*resourcepermissions.Service
|
||||
}
|
||||
|
||||
var (
|
||||
TeamMemberActions = []string{
|
||||
accesscontrol.ActionTeamsRead,
|
||||
}
|
||||
|
||||
TeamAdminActions = []string{
|
||||
accesscontrol.ActionTeamsRead,
|
||||
accesscontrol.ActionTeamsDelete,
|
||||
accesscontrol.ActionTeamsWrite,
|
||||
accesscontrol.ActionTeamsPermissionsRead,
|
||||
accesscontrol.ActionTeamsPermissionsWrite,
|
||||
}
|
||||
)
|
||||
|
||||
func ProvideTeamPermissions(
|
||||
cfg *setting.Cfg, features featuremgmt.FeatureToggles, router routing.RouteRegister, sql db.DB,
|
||||
ac accesscontrol.AccessControl, license licensing.Licensing, service accesscontrol.Service,
|
||||
teamService team.Service, userService user.Service, actionSetService resourcepermissions.ActionSetService,
|
||||
) (*TeamPermissionsService, error) {
|
||||
options := resourcepermissions.Options{
|
||||
Resource: "teams",
|
||||
ResourceAttribute: "id",
|
||||
OnlyManaged: true,
|
||||
ResourceValidator: func(ctx context.Context, orgID int64, resourceID string) error {
|
||||
id, err := strconv.ParseInt(resourceID, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = teamService.GetTeamByID(context.Background(), &team.GetTeamByIDQuery{
|
||||
OrgID: orgID,
|
||||
ID: id,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
Assignments: resourcepermissions.Assignments{
|
||||
Users: true,
|
||||
Teams: false,
|
||||
BuiltInRoles: false,
|
||||
},
|
||||
PermissionsToActions: map[string][]string{
|
||||
"Member": TeamMemberActions,
|
||||
"Admin": TeamAdminActions,
|
||||
},
|
||||
ReaderRoleName: "Team permission reader",
|
||||
WriterRoleName: "Team permission writer",
|
||||
RoleGroup: "Teams",
|
||||
OnSetUser: func(session *db.Session, orgID int64, user accesscontrol.User, resourceID, permission string) error {
|
||||
teamId, err := strconv.ParseInt(resourceID, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch permission {
|
||||
case "Member":
|
||||
return teamimpl.AddOrUpdateTeamMemberHook(session, user.ID, orgID, teamId, user.IsExternal, 0)
|
||||
case "Admin":
|
||||
return teamimpl.AddOrUpdateTeamMemberHook(session, user.ID, orgID, teamId, user.IsExternal, dashboardaccess.PERMISSION_ADMIN)
|
||||
case "":
|
||||
return teamimpl.RemoveTeamMemberHook(session, &team.RemoveTeamMemberCommand{
|
||||
OrgID: orgID,
|
||||
UserID: user.ID,
|
||||
TeamID: teamId,
|
||||
})
|
||||
default:
|
||||
return fmt.Errorf("invalid team permission type %s", permission)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
srv, err := resourcepermissions.New(cfg, options, features, router, license, ac, service, sql, teamService, userService, actionSetService)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &TeamPermissionsService{srv}, nil
|
||||
}
|
@ -691,7 +691,7 @@ func (s *store) createPermissions(sess *db.Session, roleID int64, cmd SetResourc
|
||||
|
||||
// if we have actionset feature enabled and are only working with action sets
|
||||
// skip adding the missing actions to the permissions table
|
||||
if !(s.shouldStoreActionSet(resource, permission) && s.cfg.OnlyStoreAccessActionSets) {
|
||||
if !(s.shouldStoreActionSet(resource, permission) && s.cfg.RBAC.OnlyStoreAccessActionSets) {
|
||||
for action := range missingActions {
|
||||
p := managedPermission(action, resource, resourceID, resourceAttribute)
|
||||
p.RoleID = roleID
|
||||
|
@ -488,6 +488,15 @@ func (dr *DashboardServiceImpl) GetDashboardsByPluginID(ctx context.Context, que
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) setDefaultPermissions(ctx context.Context, dto *dashboards.SaveDashboardDTO, dash *dashboards.Dashboard, provisioned bool) {
|
||||
resource := "dashboard"
|
||||
if dash.IsFolder {
|
||||
resource = "folder"
|
||||
}
|
||||
|
||||
if !dr.cfg.RBAC.PermissionsOnCreation(resource) {
|
||||
return
|
||||
}
|
||||
|
||||
metrics.MFolderIDsServiceCount.WithLabelValues(metrics.Dashboard).Inc()
|
||||
// nolint:staticcheck
|
||||
inFolder := dash.FolderID > 0
|
||||
@ -524,6 +533,10 @@ func (dr *DashboardServiceImpl) setDefaultPermissions(ctx context.Context, dto *
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) setDefaultFolderPermissions(ctx context.Context, cmd *folder.CreateFolderCommand, f *folder.Folder, provisioned bool) {
|
||||
if !dr.cfg.RBAC.PermissionsOnCreation("folder") {
|
||||
return
|
||||
}
|
||||
|
||||
inFolder := f.ParentUID != ""
|
||||
var permissions []accesscontrol.SetResourcePermissionCommand
|
||||
|
||||
|
@ -280,20 +280,24 @@ func (s *Service) AddDataSource(ctx context.Context, cmd *datasources.AddDataSou
|
||||
return err
|
||||
}
|
||||
|
||||
// This belongs in Data source permissions, and we probably want
|
||||
// to do this with a hook in the store and rollback on fail.
|
||||
// We can't use events, because there's no way to communicate
|
||||
// failure, and we want "not being able to set default perms"
|
||||
// to fail the creation.
|
||||
permissions := []accesscontrol.SetResourcePermissionCommand{
|
||||
{BuiltinRole: "Viewer", Permission: "Query"},
|
||||
{BuiltinRole: "Editor", Permission: "Query"},
|
||||
if s.cfg.RBAC.PermissionsOnCreation("datasource") {
|
||||
// This belongs in Data source permissions, and we probably want
|
||||
// to do this with a hook in the store and rollback on fail.
|
||||
// We can't use events, because there's no way to communicate
|
||||
// failure, and we want "not being able to set default perms"
|
||||
// to fail the creation.
|
||||
permissions := []accesscontrol.SetResourcePermissionCommand{
|
||||
{BuiltinRole: "Viewer", Permission: "Query"},
|
||||
{BuiltinRole: "Editor", Permission: "Query"},
|
||||
}
|
||||
if cmd.UserID != 0 {
|
||||
permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{UserID: cmd.UserID, Permission: "Admin"})
|
||||
}
|
||||
_, err = s.permissionsService.SetPermissions(ctx, cmd.OrgID, dataSource.UID, permissions...)
|
||||
return err
|
||||
}
|
||||
if cmd.UserID != 0 {
|
||||
permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{UserID: cmd.UserID, Permission: "Admin"})
|
||||
}
|
||||
_, err = s.permissionsService.SetPermissions(ctx, cmd.OrgID, dataSource.UID, permissions...)
|
||||
return err
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/ini.v1"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
@ -570,7 +571,11 @@ func TestService_DeleteDataSource(t *testing.T) {
|
||||
permissionSvc.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil).Once()
|
||||
permissionSvc.On("DeleteResourcePermissions", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
|
||||
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, &setting.Cfg{}, featuremgmt.WithFeatures(), acmock.New(), permissionSvc, quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil)
|
||||
f := ini.Empty()
|
||||
f.Section("rbac").Key("resources_with_managed_permissions_on_creation").SetValue("datasource")
|
||||
cfg, err := setting.NewCfgFromINIFile(f)
|
||||
require.NoError(t, err)
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), permissionSvc, quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// First add the datasource
|
||||
|
@ -7,10 +7,10 @@ import (
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/middleware/requestmeta"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/authn"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
@ -98,25 +98,25 @@ func (api *ServiceAccountsAPI) CreateServiceAccount(c *contextmodel.ReqContext)
|
||||
return response.ErrOrFallback(http.StatusInternalServerError, "Failed to create service account", err)
|
||||
}
|
||||
|
||||
namespace, identifier := c.SignedInUser.GetNamespacedID()
|
||||
if api.cfg.RBAC.PermissionsOnCreation("service-account") {
|
||||
if c.SignedInUser.GetID().IsNamespace(authn.NamespaceUser) {
|
||||
userID, err := c.SignedInUser.GetID().ParseInt()
|
||||
if err != nil {
|
||||
return response.Error(http.StatusInternalServerError, "Failed to parse user id", err)
|
||||
}
|
||||
|
||||
if namespace == identity.NamespaceUser {
|
||||
userID, err := identity.IntIdentifier(namespace, identifier)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusInternalServerError, "Failed to parse user id", err)
|
||||
}
|
||||
if _, err := api.permissionService.SetUserPermission(c.Req.Context(),
|
||||
c.SignedInUser.GetOrgID(), accesscontrol.User{ID: userID},
|
||||
strconv.FormatInt(serviceAccount.Id, 10), "Admin"); err != nil {
|
||||
return response.Error(http.StatusInternalServerError, "Failed to set permissions for service account creator", err)
|
||||
}
|
||||
|
||||
if _, err := api.permissionService.SetUserPermission(c.Req.Context(),
|
||||
c.SignedInUser.GetOrgID(), accesscontrol.User{ID: userID},
|
||||
strconv.FormatInt(serviceAccount.Id, 10), "Admin"); err != nil {
|
||||
return response.Error(http.StatusInternalServerError, "Failed to set permissions for service account creator", err)
|
||||
// Clear permission cache for the user who's created the service account, so that new permissions are fetched for their next call
|
||||
// Required for cases when caller wants to immediately interact with the newly created object
|
||||
api.accesscontrolService.ClearUserPermissionCache(c.SignedInUser)
|
||||
}
|
||||
}
|
||||
|
||||
// Clear permission cache for the user who's created the service account, so that new permissions are fetched for their next call
|
||||
// Required for cases when caller wants to immediately interact with the newly created object
|
||||
api.accesscontrolService.ClearUserPermissionCache(c.SignedInUser)
|
||||
|
||||
return response.JSON(http.StatusCreated, serviceAccount)
|
||||
}
|
||||
|
||||
|
@ -322,9 +322,6 @@ type Cfg struct {
|
||||
// GrafanaJavascriptAgent config
|
||||
GrafanaJavascriptAgent GrafanaJavascriptAgent
|
||||
|
||||
// accessactionsets
|
||||
OnlyStoreAccessActionSets bool
|
||||
|
||||
// Data sources
|
||||
DataSourceLimit int
|
||||
// Number of queries to be executed concurrently. Only for the datasource supports concurrency.
|
||||
@ -467,14 +464,7 @@ type Cfg struct {
|
||||
OAuth2ServerGeneratedKeyTypeForClient string
|
||||
OAuth2ServerAccessTokenLifespan time.Duration
|
||||
|
||||
// Access Control
|
||||
RBACPermissionCache bool
|
||||
// Enable Permission validation during role creation and provisioning
|
||||
RBACPermissionValidationEnabled bool
|
||||
// Reset basic roles permissions on start-up
|
||||
RBACResetBasicRoles bool
|
||||
// RBAC single organization. This configuration option is subject to change.
|
||||
RBACSingleOrganization bool
|
||||
RBAC RBACSettings
|
||||
|
||||
Zanzana ZanzanaSettings
|
||||
|
||||
@ -1116,7 +1106,7 @@ func (cfg *Cfg) parseINIFile(iniFile *ini.File) error {
|
||||
|
||||
readOAuth2ServerSettings(cfg)
|
||||
|
||||
readAccessControlSettings(iniFile, cfg)
|
||||
cfg.readRBACSettings()
|
||||
|
||||
cfg.readZanzanaSettings()
|
||||
|
||||
@ -1657,15 +1647,6 @@ func readAuthSettings(iniFile *ini.File, cfg *Cfg) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func readAccessControlSettings(iniFile *ini.File, cfg *Cfg) {
|
||||
rbac := iniFile.Section("rbac")
|
||||
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)
|
||||
cfg.OnlyStoreAccessActionSets = rbac.Key("only_store_access_action_sets").MustBool(false)
|
||||
}
|
||||
|
||||
func readOAuth2ServerSettings(cfg *Cfg) {
|
||||
oauth2Srv := cfg.SectionWithEnvOverrides("oauth2_server")
|
||||
cfg.OAuth2ServerEnabled = oauth2Srv.Key("enabled").MustBool(false)
|
||||
|
61
pkg/setting/settings_rbac.go
Normal file
61
pkg/setting/settings_rbac.go
Normal file
@ -0,0 +1,61 @@
|
||||
package setting
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
type RBACSettings struct {
|
||||
// Enable permission cache
|
||||
PermissionCache bool
|
||||
// Enable Permission validation during role creation and provisioning
|
||||
PermissionValidationEnabled bool
|
||||
// Reset basic roles permissions on start-up
|
||||
ResetBasicRoles bool
|
||||
// RBAC single organization. This configuration option is subject to change.
|
||||
SingleOrganization bool
|
||||
|
||||
OnlyStoreAccessActionSets bool
|
||||
|
||||
// set of resources that should generate managed permissions when created
|
||||
resourcesWithPermissionsOnCreation map[string]struct{}
|
||||
|
||||
// set of resources that should we should seed wildcard scopes for
|
||||
resourcesWithWildcardSeed map[string]struct{}
|
||||
}
|
||||
|
||||
func (cfg *Cfg) readRBACSettings() {
|
||||
s := RBACSettings{}
|
||||
|
||||
rbac := cfg.Raw.Section("rbac")
|
||||
s.PermissionCache = rbac.Key("permission_cache").MustBool(true)
|
||||
s.PermissionValidationEnabled = rbac.Key("permission_validation_enabled").MustBool(false)
|
||||
s.ResetBasicRoles = rbac.Key("reset_basic_roles").MustBool(false)
|
||||
s.SingleOrganization = rbac.Key("single_organization").MustBool(false)
|
||||
s.OnlyStoreAccessActionSets = rbac.Key("only_store_access_action_sets").MustBool(false)
|
||||
|
||||
// List of resources to generate managed permissions for upon resource creation (dashboard, folder, service-account, datasource)
|
||||
resources := util.SplitString(rbac.Key("resources_with_managed_permissions_on_creation").MustString("dashboard, folder, service-account, datasource"))
|
||||
s.resourcesWithPermissionsOnCreation = map[string]struct{}{}
|
||||
for _, resource := range resources {
|
||||
s.resourcesWithPermissionsOnCreation[resource] = struct{}{}
|
||||
}
|
||||
|
||||
// List of resources to seed managed permission wildcards for (dashboard, folder, datasource)
|
||||
resources = util.SplitString(rbac.Key("resources_with_seeded_wildcard_access").MustString(""))
|
||||
s.resourcesWithWildcardSeed = map[string]struct{}{}
|
||||
for _, resource := range resources {
|
||||
s.resourcesWithWildcardSeed[resource] = struct{}{}
|
||||
}
|
||||
|
||||
cfg.RBAC = s
|
||||
}
|
||||
|
||||
func (r RBACSettings) PermissionsOnCreation(resource string) bool {
|
||||
_, ok := r.resourcesWithPermissionsOnCreation[resource]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (r RBACSettings) PermissionsWildcardSeed(resource string) bool {
|
||||
_, ok := r.resourcesWithWildcardSeed[resource]
|
||||
return ok
|
||||
}
|
Loading…
Reference in New Issue
Block a user