Dashboards: Use the common service authorizer (#111571)

* authorizer

* authorizer
This commit is contained in:
Ryan McKinley
2025-10-17 10:03:35 +03:00
committed by GitHub
parent 18e727ee7f
commit 1a372e2dec
4 changed files with 5 additions and 136 deletions
-129
View File
@@ -1,129 +0,0 @@
package dashboard
import (
"context"
"k8s.io/apiserver/pkg/authorization/authorizer"
"github.com/grafana/authlib/types"
dashv0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/libraryelements"
)
func newLegacyAuthorizer(ac accesscontrol.AccessControl) authorizer.Authorizer {
return authorizer.AuthorizerFunc(
func(ctx context.Context, attr authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
// Note that we will return Allow more than expected.
// This is because we do NOT want to hit the RoleAuthorizer that would be evaluated afterwards.
if !attr.IsResourceRequest() {
return authorizer.DecisionDeny, "unexpected non-resource request", nil
}
user, err := identity.GetRequester(ctx)
if err != nil {
return authorizer.DecisionDeny, "error getting requester", err
}
ns := attr.GetNamespace()
if ns == "" {
return authorizer.DecisionDeny, "expected namespace", nil
}
info, err := types.ParseNamespace(attr.GetNamespace())
if err != nil {
return authorizer.DecisionDeny, "error reading org from namespace", err
}
// Validate organization access before we possibly step out here.
if user.GetOrgID() != info.OrgID {
return authorizer.DecisionDeny, "org mismatch", dashboards.ErrUserIsNotSignedInToOrg
}
// Determine if this is a library panel or dashboard resource
resource := attr.GetResource()
isLibraryPanel := resource == dashv0.LIBRARY_PANEL_RESOURCE
if isLibraryPanel {
return authorizeLibraryPanel(ctx, ac, user, attr)
} else {
return authorizeDashboard(ctx, ac, user, attr)
}
})
}
func authorizeLibraryPanel(ctx context.Context, ac accesscontrol.AccessControl, user identity.Requester, attr authorizer.Attributes) (authorizer.Decision, string, error) {
switch attr.GetVerb() {
case "list", "search":
// Detailed read permissions are handled by authz, this just checks whether the user can ready *any* library panel
ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(libraryelements.ActionLibraryPanelsRead))
if !ok || err != nil {
return authorizer.DecisionDeny, "can not read any library panels", err
}
case "create":
// TODO: uncomment this when we implement create :)
//
// Detailed create permissions are handled by authz, this just checks whether the user can create *any* library panel
// ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(libraryelements.ActionLibraryPanelsCreate))
// if !ok || err != nil {
// return authorizer.DecisionDeny, "can not create any library panels", err
// }
return authorizer.DecisionDeny, "can not create any library panels", nil
case "get":
ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(libraryelements.ActionLibraryPanelsRead, libraryelements.ScopeLibraryPanelsProvider.GetResourceScopeUID(attr.GetName())))
if !ok || err != nil {
return authorizer.DecisionDeny, "can not view library panel", err
}
case "update", "patch":
ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(libraryelements.ActionLibraryPanelsWrite, libraryelements.ScopeLibraryPanelsProvider.GetResourceScopeUID(attr.GetName())))
if !ok || err != nil {
return authorizer.DecisionDeny, "can not edit library panel", err
}
case "delete":
ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(libraryelements.ActionLibraryPanelsDelete, libraryelements.ScopeLibraryPanelsProvider.GetResourceScopeUID(attr.GetName())))
if !ok || err != nil {
return authorizer.DecisionDeny, "can not delete library panel", err
}
default:
return authorizer.DecisionDeny, "unsupported verb for library panels", nil
}
return authorizer.DecisionAllow, "", nil
}
func authorizeDashboard(ctx context.Context, ac accesscontrol.AccessControl, user identity.Requester, attr authorizer.Attributes) (authorizer.Decision, string, error) {
switch attr.GetVerb() {
case "list", "search":
// Detailed read permissions are handled by authz, this just checks whether the user can ready *any* dashboard
ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(dashboards.ActionDashboardsRead))
if !ok || err != nil {
return authorizer.DecisionDeny, "can not read any dashboards", err
}
case "create":
// Detailed create permissions are handled by authz, this just checks whether the user can create *any* dashboard
ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(dashboards.ActionDashboardsCreate))
if !ok || err != nil {
return authorizer.DecisionDeny, "can not create any dashboards", err
}
case "get":
ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(dashboards.ActionDashboardsRead, dashboards.ScopeDashboardsProvider.GetResourceScopeUID(attr.GetName())))
if !ok || err != nil {
return authorizer.DecisionDeny, "can not view dashboard", err
}
case "update", "patch":
ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(dashboards.ActionDashboardsWrite, dashboards.ScopeDashboardsProvider.GetResourceScopeUID(attr.GetName())))
if !ok || err != nil {
return authorizer.DecisionDeny, "can not edit dashboard", err
}
case "delete":
ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(dashboards.ActionDashboardsDelete, dashboards.ScopeDashboardsProvider.GetResourceScopeUID(attr.GetName())))
if !ok || err != nil {
return authorizer.DecisionDeny, "can not delete dashboard", err
}
default:
return authorizer.DecisionDeny, "unsupported verb for dashboards", nil
}
return authorizer.DecisionAllow, "", nil
}
@@ -11,7 +11,6 @@ import (
"k8s.io/apiserver/pkg/registry/rest"
"github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/apimachinery/utils"
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
+3 -5
View File
@@ -42,7 +42,7 @@ import (
"github.com/grafana/grafana/pkg/registry/apis/dashboard/legacysearcher"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apiserver"
authsvc "github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer"
grafanaauthorizer "github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer"
"github.com/grafana/grafana/pkg/services/apiserver/builder"
"github.com/grafana/grafana/pkg/services/apiserver/client"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
@@ -92,7 +92,6 @@ type DashboardsAPIBuilder struct {
dashboardService dashboards.DashboardService
features featuremgmt.FeatureToggles
authorizer authorizer.Authorizer
accessControl accesscontrol.AccessControl
accessClient authlib.AccessClient
legacy *DashboardStorage
@@ -143,7 +142,6 @@ func RegisterAPIService(
folderClient := client.NewK8sHandler(dual, request.GetNamespaceMapper(cfg), folders.FolderResourceInfo.GroupVersionResource(), restConfigProvider.GetRestConfig, dashStore, userService, unified, sorter, features)
builder := &DashboardsAPIBuilder{
authorizer: newLegacyAuthorizer(accessControl),
dashboardService: dashboardService,
dashboardPermissions: dashboardPermissions,
dashboardPermissionsSvc: dashboardPermissionsSvc,
@@ -179,7 +177,6 @@ func NewAPIService(ac authlib.AccessClient, features featuremgmt.FeatureToggles,
return &DashboardsAPIBuilder{
minRefreshInterval: "10s",
accessClient: ac,
authorizer: authsvc.NewResourceAuthorizer(ac),
features: features,
dashboardService: &dashsvc.DashboardServiceImpl{}, // for validation helpers only
folderClientProvider: folderClientProvider,
@@ -764,8 +761,9 @@ func (b *DashboardsAPIBuilder) GetAPIRoutes(gv schema.GroupVersion) *builder.API
return b.search.GetAPIRoutes(defs)
}
// The default authorizer is fine because authorization happens in storage where we know the parent folder
func (b *DashboardsAPIBuilder) GetAuthorizer() authorizer.Authorizer {
return b.authorizer
return grafanaauthorizer.NewServiceAuthorizer()
}
func (b *DashboardsAPIBuilder) verifyFolderAccessPermissions(ctx context.Context, user identity.Requester, folderIds ...string) error {
+2 -1
View File
@@ -181,7 +181,8 @@ func newFolderTranslation() translation {
func NewMapperRegistry() MapperRegistry {
mapper := mapper(map[string]map[string]translation{
"dashboard.grafana.app": {
"dashboards": newDashboardTranslation(),
"dashboards": newDashboardTranslation(),
"librarypanels": newResourceTranslation("library.panels", "uid", true, false),
},
"folder.grafana.app": {
"folders": newFolderTranslation(),