mirror of
https://github.com/grafana/grafana.git
synced 2025-09-21 21:15:29 +08:00
RBAC: Remove dashboard guardians pt 2 (#102556)
* remove NewByDashboard guardian * remove unused authorizer * more cleanup * simplify canAdmin evaluation
This commit is contained in:
@ -151,9 +151,7 @@ func (hs *HTTPServer) GetDashboard(c *contextmodel.ReqContext) response.Response
|
|||||||
}
|
}
|
||||||
deleteEvaluator := accesscontrol.EvalPermission(dashboards.ActionDashboardsDelete, dashScope)
|
deleteEvaluator := accesscontrol.EvalPermission(dashboards.ActionDashboardsDelete, dashScope)
|
||||||
canDelete, _ := hs.AccessControl.Evaluate(ctx, c.SignedInUser, deleteEvaluator)
|
canDelete, _ := hs.AccessControl.Evaluate(ctx, c.SignedInUser, deleteEvaluator)
|
||||||
adminEvaluator := accesscontrol.EvalAll(
|
adminEvaluator := accesscontrol.EvalPermission(dashboards.ActionDashboardsPermissionsWrite, dashScope)
|
||||||
accesscontrol.EvalPermission(dashboards.ActionDashboardsPermissionsRead, dashScope),
|
|
||||||
accesscontrol.EvalPermission(dashboards.ActionDashboardsPermissionsWrite, dashScope))
|
|
||||||
canAdmin, _ := hs.AccessControl.Evaluate(ctx, c.SignedInUser, adminEvaluator)
|
canAdmin, _ := hs.AccessControl.Evaluate(ctx, c.SignedInUser, adminEvaluator)
|
||||||
|
|
||||||
isStarred, err := hs.isDashboardStarredByUser(c, dash.ID)
|
isStarred, err := hs.isDashboardStarredByUser(c, dash.ID)
|
||||||
|
@ -1,91 +0,0 @@
|
|||||||
package dashboard
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
|
||||||
|
|
||||||
claims "github.com/grafana/authlib/types"
|
|
||||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
|
||||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
|
||||||
"github.com/grafana/grafana/pkg/services/guardian"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetAuthorizer(dashboardService dashboards.DashboardService, l log.Logger) authorizer.Authorizer {
|
|
||||||
return authorizer.AuthorizerFunc(
|
|
||||||
func(ctx context.Context, attr authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
|
|
||||||
// Use the standard authorizer
|
|
||||||
if !attr.IsResourceRequest() {
|
|
||||||
return authorizer.DecisionNoOpinion, "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := identity.GetRequester(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return authorizer.DecisionDeny, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow search and list requests
|
|
||||||
if attr.GetResource() == "search" || attr.GetName() == "" {
|
|
||||||
return authorizer.DecisionNoOpinion, "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ns := attr.GetNamespace()
|
|
||||||
if ns == "" {
|
|
||||||
return authorizer.DecisionDeny, "expected namespace", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
info, err := claims.ParseNamespace(attr.GetNamespace())
|
|
||||||
if err != nil {
|
|
||||||
return authorizer.DecisionDeny, "error reading org from namespace", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// expensive path to lookup permissions for a single dashboard
|
|
||||||
dto, err := dashboardService.GetDashboard(ctx, &dashboards.GetDashboardQuery{
|
|
||||||
UID: attr.GetName(),
|
|
||||||
OrgID: info.OrgID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return authorizer.DecisionDeny, "error loading dashboard", err
|
|
||||||
}
|
|
||||||
|
|
||||||
ok := false
|
|
||||||
guardian, err := guardian.NewByDashboard(ctx, dto, info.OrgID, user)
|
|
||||||
if err != nil {
|
|
||||||
return authorizer.DecisionDeny, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch attr.GetVerb() {
|
|
||||||
case "get":
|
|
||||||
ok, err = guardian.CanView()
|
|
||||||
if !ok || err != nil {
|
|
||||||
return authorizer.DecisionDeny, "can not view dashboard", err
|
|
||||||
}
|
|
||||||
case "create":
|
|
||||||
fallthrough
|
|
||||||
case "post":
|
|
||||||
ok, err = guardian.CanSave() // vs Edit?
|
|
||||||
if !ok || err != nil {
|
|
||||||
return authorizer.DecisionDeny, "can not save dashboard", err
|
|
||||||
}
|
|
||||||
case "update":
|
|
||||||
fallthrough
|
|
||||||
case "patch":
|
|
||||||
fallthrough
|
|
||||||
case "put":
|
|
||||||
ok, err = guardian.CanEdit() // vs Save
|
|
||||||
if !ok || err != nil {
|
|
||||||
return authorizer.DecisionDeny, "can not edit dashboard", err
|
|
||||||
}
|
|
||||||
case "delete":
|
|
||||||
ok, err = guardian.CanDelete()
|
|
||||||
if !ok || err != nil {
|
|
||||||
return authorizer.DecisionDeny, "can not delete dashboard", err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
l.Info("unknown verb", "verb", attr.GetVerb())
|
|
||||||
return authorizer.DecisionNoOpinion, "unsupported verb", nil // Unknown verb
|
|
||||||
}
|
|
||||||
return authorizer.DecisionAllow, "", nil
|
|
||||||
})
|
|
||||||
}
|
|
@ -19,7 +19,6 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||||
"github.com/grafana/grafana/pkg/services/guardian"
|
|
||||||
"github.com/grafana/grafana/pkg/storage/unified/apistore"
|
"github.com/grafana/grafana/pkg/storage/unified/apistore"
|
||||||
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||||
)
|
)
|
||||||
@ -87,7 +86,7 @@ func (r *DTOConnector) ProducesObject(verb string) interface{} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *DTOConnector) Connect(ctx context.Context, name string, opts runtime.Object, responder rest.Responder) (http.Handler, error) {
|
func (r *DTOConnector) Connect(ctx context.Context, name string, opts runtime.Object, responder rest.Responder) (http.Handler, error) {
|
||||||
info, err := request.NamespaceInfoFrom(ctx, true)
|
_, err := request.NamespaceInfoFrom(ctx, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -128,33 +127,22 @@ func (r *DTOConnector) Connect(ctx context.Context, name string, opts runtime.Ob
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate access information -- needed to help smooth transition from /api/dashboard format
|
dashScope := dashboards.ScopeDashboardsProvider.GetResourceScopeUID(name)
|
||||||
dto := &dashboards.Dashboard{
|
evaluator := accesscontrol.EvalPermission(dashboards.ActionDashboardsRead, dashScope)
|
||||||
UID: name,
|
canView, err := r.accessControl.Evaluate(ctx, user, evaluator)
|
||||||
OrgID: info.OrgID,
|
|
||||||
ID: obj.GetDeprecatedInternalID(), // nolint:staticcheck
|
|
||||||
}
|
|
||||||
manager, ok := obj.GetManagerProperties()
|
|
||||||
if ok && manager.Kind == utils.ManagerKindPlugin {
|
|
||||||
dto.PluginID = manager.Identity
|
|
||||||
}
|
|
||||||
|
|
||||||
guardian, err := guardian.NewByDashboard(ctx, dto, info.OrgID, user)
|
|
||||||
if err != nil {
|
|
||||||
responder.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
canView, err := guardian.CanView()
|
|
||||||
if err != nil || !canView {
|
if err != nil || !canView {
|
||||||
responder.Error(fmt.Errorf("not allowed to view"))
|
responder.Error(fmt.Errorf("not allowed to view"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
access := &dashboard.DashboardAccess{}
|
access := &dashboard.DashboardAccess{}
|
||||||
access.CanEdit, _ = guardian.CanEdit()
|
writeEvaluator := accesscontrol.EvalPermission(dashboards.ActionDashboardsWrite, dashScope)
|
||||||
access.CanSave, _ = guardian.CanSave()
|
access.CanSave, _ = r.accessControl.Evaluate(ctx, user, writeEvaluator)
|
||||||
access.CanAdmin, _ = guardian.CanAdmin()
|
access.CanEdit = access.CanSave
|
||||||
access.CanDelete, _ = guardian.CanDelete()
|
adminEvaluator := accesscontrol.EvalPermission(dashboards.ActionDashboardsPermissionsWrite, dashScope)
|
||||||
|
access.CanAdmin, _ = r.accessControl.Evaluate(ctx, user, adminEvaluator)
|
||||||
|
deleteEvaluator := accesscontrol.EvalPermission(dashboards.ActionDashboardsDelete, dashScope)
|
||||||
|
access.CanDelete, _ = r.accessControl.Evaluate(ctx, user, deleteEvaluator)
|
||||||
access.CanStar = user.IsIdentityType(claims.TypeUser)
|
access.CanStar = user.IsIdentityType(claims.TypeUser)
|
||||||
|
|
||||||
access.AnnotationsPermissions = &dashboard.AnnotationPermission{}
|
access.AnnotationsPermissions = &dashboard.AnnotationPermission{}
|
||||||
|
@ -7,7 +7,6 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/apimachinery/errutil"
|
"github.com/grafana/grafana/pkg/apimachinery/errutil"
|
||||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||||
"github.com/grafana/grafana/pkg/infra/metrics"
|
"github.com/grafana/grafana/pkg/infra/metrics"
|
||||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
|
||||||
"github.com/grafana/grafana/pkg/services/folder"
|
"github.com/grafana/grafana/pkg/services/folder"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -34,12 +33,6 @@ var New = func(ctx context.Context, dashId int64, orgId int64, user identity.Req
|
|||||||
panic("no guardian factory implementation provided")
|
panic("no guardian factory implementation provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewByDashboard factory for creating a new dashboard guardian instance
|
|
||||||
// When using access control this function is replaced on startup and the AccessControlDashboardGuardian is returned
|
|
||||||
var NewByDashboard = func(ctx context.Context, dash *dashboards.Dashboard, orgId int64, user identity.Requester) (DashboardGuardian, error) {
|
|
||||||
panic("no guardian factory implementation provided")
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewByFolderUID factory for creating a new folder guardian instance
|
// NewByFolderUID factory for creating a new folder guardian instance
|
||||||
// When using access control this function is replaced on startup and the AccessControlDashboardGuardian is returned
|
// When using access control this function is replaced on startup and the AccessControlDashboardGuardian is returned
|
||||||
var NewByFolderUID = func(ctx context.Context, folderUID string, orgId int64, user identity.Requester) (DashboardGuardian, error) {
|
var NewByFolderUID = func(ctx context.Context, folderUID string, orgId int64, user identity.Requester) (DashboardGuardian, error) {
|
||||||
@ -108,13 +101,6 @@ func MockDashboardGuardian(mock *FakeDashboardGuardian) {
|
|||||||
mock.User = user
|
mock.User = user
|
||||||
return mock, nil
|
return mock, nil
|
||||||
}
|
}
|
||||||
NewByDashboard = func(_ context.Context, dash *dashboards.Dashboard, orgId int64, user identity.Requester) (DashboardGuardian, error) {
|
|
||||||
mock.OrgID = orgId
|
|
||||||
mock.DashUID = dash.UID
|
|
||||||
mock.DashID = dash.ID
|
|
||||||
mock.User = user
|
|
||||||
return mock, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
NewByFolderUID = func(_ context.Context, folderUID string, orgId int64, user identity.Requester) (DashboardGuardian, error) {
|
NewByFolderUID = func(_ context.Context, folderUID string, orgId int64, user identity.Requester) (DashboardGuardian, error) {
|
||||||
mock.OrgID = orgId
|
mock.OrgID = orgId
|
||||||
|
@ -31,10 +31,6 @@ func InitAccessControlGuardian(
|
|||||||
return NewAccessControlDashboardGuardian(ctx, cfg, dashId, user, ac, dashboardService, folderService, logger)
|
return NewAccessControlDashboardGuardian(ctx, cfg, dashId, user, ac, dashboardService, folderService, logger)
|
||||||
}
|
}
|
||||||
|
|
||||||
NewByDashboard = func(ctx context.Context, dash *dashboards.Dashboard, orgId int64, user identity.Requester) (DashboardGuardian, error) {
|
|
||||||
return NewAccessControlDashboardGuardianByDashboard(ctx, cfg, dash, user, ac, dashboardService, folderService, logger)
|
|
||||||
}
|
|
||||||
|
|
||||||
NewByFolderUID = func(ctx context.Context, folderUID string, orgId int64, user identity.Requester) (DashboardGuardian, error) {
|
NewByFolderUID = func(ctx context.Context, folderUID string, orgId int64, user identity.Requester) (DashboardGuardian, error) {
|
||||||
return NewAccessControlFolderGuardianByUID(ctx, cfg, folderUID, user, ac, dashboardService, folderService)
|
return NewAccessControlFolderGuardianByUID(ctx, cfg, folderUID, user, ac, dashboardService, folderService)
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,8 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||||
"github.com/grafana/grafana/pkg/infra/db"
|
"github.com/grafana/grafana/pkg/infra/db"
|
||||||
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||||
"github.com/grafana/grafana/pkg/services/guardian"
|
|
||||||
"github.com/grafana/grafana/pkg/services/live/model"
|
"github.com/grafana/grafana/pkg/services/live/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -63,6 +63,7 @@ type DashboardHandler struct {
|
|||||||
ClientCount model.ChannelClientCount
|
ClientCount model.ChannelClientCount
|
||||||
Store db.DB
|
Store db.DB
|
||||||
DashboardService dashboards.DashboardService
|
DashboardService dashboards.DashboardService
|
||||||
|
AccessControl accesscontrol.AccessControl
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetHandlerForPath called on init
|
// GetHandlerForPath called on init
|
||||||
@ -77,20 +78,17 @@ func (h *DashboardHandler) OnSubscribe(ctx context.Context, user identity.Reques
|
|||||||
// make sure can view this dashboard
|
// make sure can view this dashboard
|
||||||
if len(parts) == 2 && parts[0] == "uid" {
|
if len(parts) == 2 && parts[0] == "uid" {
|
||||||
query := dashboards.GetDashboardQuery{UID: parts[1], OrgID: user.GetOrgID()}
|
query := dashboards.GetDashboardQuery{UID: parts[1], OrgID: user.GetOrgID()}
|
||||||
queryResult, err := h.DashboardService.GetDashboard(ctx, &query)
|
_, err := h.DashboardService.GetDashboard(ctx, &query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Error getting dashboard", "query", query, "error", err)
|
logger.Error("Error getting dashboard", "query", query, "error", err)
|
||||||
return model.SubscribeReply{}, backend.SubscribeStreamStatusNotFound, nil
|
return model.SubscribeReply{}, backend.SubscribeStreamStatusNotFound, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
dash := queryResult
|
evaluator := accesscontrol.EvalPermission(dashboards.ActionDashboardsRead, dashboards.ScopeDashboardsProvider.GetResourceScopeUID(parts[1]))
|
||||||
guard, err := guardian.NewByDashboard(ctx, dash, user.GetOrgID(), user)
|
canView, err := h.AccessControl.Evaluate(ctx, user, evaluator)
|
||||||
if err != nil {
|
if err != nil || !canView {
|
||||||
return model.SubscribeReply{}, backend.SubscribeStreamStatusPermissionDenied, err
|
return model.SubscribeReply{}, backend.SubscribeStreamStatusPermissionDenied, err
|
||||||
}
|
}
|
||||||
if canView, err := guard.CanView(); err != nil || !canView {
|
|
||||||
return model.SubscribeReply{}, backend.SubscribeStreamStatusPermissionDenied, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return model.SubscribeReply{
|
return model.SubscribeReply{
|
||||||
Presence: true,
|
Presence: true,
|
||||||
@ -119,19 +117,14 @@ func (h *DashboardHandler) OnPublish(ctx context.Context, requester identity.Req
|
|||||||
return model.PublishReply{}, backend.PublishStreamStatusNotFound, fmt.Errorf("ignore???")
|
return model.PublishReply{}, backend.PublishStreamStatusNotFound, fmt.Errorf("ignore???")
|
||||||
}
|
}
|
||||||
query := dashboards.GetDashboardQuery{UID: parts[1], OrgID: requester.GetOrgID()}
|
query := dashboards.GetDashboardQuery{UID: parts[1], OrgID: requester.GetOrgID()}
|
||||||
queryResult, err := h.DashboardService.GetDashboard(ctx, &query)
|
_, err = h.DashboardService.GetDashboard(ctx, &query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Unknown dashboard", "query", query)
|
logger.Error("Unknown dashboard", "query", query)
|
||||||
return model.PublishReply{}, backend.PublishStreamStatusNotFound, nil
|
return model.PublishReply{}, backend.PublishStreamStatusNotFound, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
guard, err := guardian.NewByDashboard(ctx, queryResult, requester.GetOrgID(), requester)
|
evaluator := accesscontrol.EvalPermission(dashboards.ActionDashboardsWrite, dashboards.ScopeDashboardsProvider.GetResourceScopeUID(parts[1]))
|
||||||
if err != nil {
|
canEdit, err := h.AccessControl.Evaluate(ctx, requester, evaluator)
|
||||||
logger.Error("Failed to create guardian", "err", err)
|
|
||||||
return model.PublishReply{}, backend.PublishStreamStatusNotFound, fmt.Errorf("internal error")
|
|
||||||
}
|
|
||||||
|
|
||||||
canEdit, err := guard.CanEdit()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return model.PublishReply{}, backend.PublishStreamStatusNotFound, fmt.Errorf("internal error")
|
return model.PublishReply{}, backend.PublishStreamStatusNotFound, fmt.Errorf("internal error")
|
||||||
}
|
}
|
||||||
|
@ -184,6 +184,7 @@ func ProvideService(plugCtxProvider *plugincontext.Provider, cfg *setting.Cfg, r
|
|||||||
ClientCount: g.ClientCount,
|
ClientCount: g.ClientCount,
|
||||||
Store: sqlStore,
|
Store: sqlStore,
|
||||||
DashboardService: dashboardService,
|
DashboardService: dashboardService,
|
||||||
|
AccessControl: accessControl,
|
||||||
}
|
}
|
||||||
g.storage = database.NewStorage(g.SQLStore, g.CacheService)
|
g.storage = database.NewStorage(g.SQLStore, g.CacheService)
|
||||||
g.GrafanaScope.Dashboards = dash
|
g.GrafanaScope.Dashboards = dash
|
||||||
|
Reference in New Issue
Block a user