Files
Stephanie Hingtgen b887e8aa05 K8s: Dashboards: Add fine grained access control checks to /apis (#104347)
---------

Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>
Co-authored-by: Gabriel MABILLE <gabriel.mabille@grafana.com>
Co-authored-by: Marco de Abreu <marco.deabreu@grafana.com>
Co-authored-by: Georges Chaudy <chaudyg@gmail.com>
2025-04-23 03:29:05 +01:00

80 lines
3.2 KiB
Go

package dashboard
import (
"context"
"k8s.io/apiserver/pkg/authorization/authorizer"
"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/accesscontrol"
"github.com/grafana/grafana/pkg/services/dashboards"
)
func GetAuthorizer(ac accesscontrol.AccessControl, l log.Logger) 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
}
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:
l.Info("unknown verb", "verb", attr.GetVerb())
return authorizer.DecisionDeny, "unsupported verb", nil // Unknown verb
}
return authorizer.DecisionAllow, "", nil
})
}