Dashboards: Use the common service authorizer (#111571)
* authorizer * authorizer
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user