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>
This commit is contained in:
Stephanie Hingtgen
2025-04-23 03:29:05 +01:00
committed by GitHub
co-authored by Ieva Gabriel MABILLE Marco de Abreu Georges Chaudy
parent 410c5ebfb7
commit b887e8aa05
10 changed files with 1647 additions and 34 deletions
+7 -2
View File
@@ -18,6 +18,7 @@ import (
authzv1 "github.com/grafana/authlib/authz/proto/v1"
"github.com/grafana/authlib/cache"
"github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
@@ -553,10 +554,14 @@ func (s *Service) checkPermission(ctx context.Context, scopeMap map[string]bool,
ctxLogger := s.logger.FromContext(ctx)
// Only check action if the request doesn't specify scope
if req.Name == "" {
if req.Name == "" && req.Verb != utils.VerbCreate {
return len(scopeMap) > 0, nil
}
if req.Verb == utils.VerbCreate && req.ParentFolder == "" {
req.ParentFolder = accesscontrol.GeneralFolderUID
}
// Wildcard grant, no further checks needed
if scopeMap["*"] {
return true, nil
@@ -568,7 +573,7 @@ func (s *Service) checkPermission(ctx context.Context, scopeMap map[string]bool,
return false, status.Error(codes.NotFound, "unsupported resource")
}
if scopeMap[t.scope(req.Name)] {
if req.Name != "" && scopeMap[t.scope(req.Name)] {
return true, nil
}
+92 -5
View File
@@ -17,6 +17,7 @@ import (
"github.com/grafana/authlib/cache"
"github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/registry/apis/iam/legacy"
@@ -127,19 +128,39 @@ func TestService_checkPermission(t *testing.T) {
expected: true,
},
{
name: "should return true if no resource is specified",
name: "should check general folder scope for root level resource creation",
permissions: []accesscontrol.Permission{
{
Action: "folders:create",
Action: "dashboards:create",
Scope: "folders:uid:general",
Kind: "folders",
Attribute: "uid",
Identifier: "general",
},
},
check: CheckRequest{
Action: "folders:create",
Group: "folder.grafana.app",
Resource: "folders",
Action: "dashboards:create",
Group: "dashboard.grafana.app",
Resource: "dashboards",
Verb: utils.VerbCreate,
},
expected: true,
},
{
name: "should fail if user doesn't have general folder scope for root level resource creation",
permissions: []accesscontrol.Permission{
{
Action: "dashboards:create",
},
},
check: CheckRequest{
Action: "dashboards:create",
Group: "dashboard.grafana.app",
Resource: "dashboards",
Verb: utils.VerbCreate,
},
expected: false,
},
{
name: "should return false if user has no permissions on resource",
permissions: []accesscontrol.Permission{},
@@ -174,6 +195,72 @@ func TestService_checkPermission(t *testing.T) {
},
expected: true,
},
{
name: "should allow creating a nested resource",
permissions: []accesscontrol.Permission{
{
Action: "dashboards:create",
Scope: "folders:uid:parent",
Kind: "folders",
Attribute: "uid",
Identifier: "parent",
},
},
folders: []store.Folder{{UID: "parent"}},
check: CheckRequest{
Action: "dashboards:create",
Group: "dashboard.grafana.app",
Resource: "dashboards",
Name: "",
ParentFolder: "parent",
Verb: utils.VerbCreate,
},
expected: true,
},
{
name: "should deny creating a nested resource",
permissions: []accesscontrol.Permission{
{
Action: "dashboards:create",
Scope: "folders:uid:parent",
Kind: "folders",
Attribute: "uid",
Identifier: "parent",
},
},
folders: []store.Folder{{UID: "parent"}},
check: CheckRequest{
Action: "dashboards:create",
Group: "dashboard.grafana.app",
Resource: "dashboards",
Name: "",
ParentFolder: "other_parent",
Verb: utils.VerbCreate,
},
expected: false,
},
{
name: "should allow if it's an any check",
permissions: []accesscontrol.Permission{
{
Action: "dashboards:read",
Scope: "folders:uid:parent",
Kind: "folders",
Attribute: "uid",
Identifier: "parent",
},
},
folders: []store.Folder{{UID: "parent"}},
check: CheckRequest{
Action: "dashboards:read",
Group: "dashboard.grafana.app",
Resource: "dashboards",
Name: "",
ParentFolder: "",
Verb: utils.VerbList,
},
expected: true,
},
}
for _, tc := range testCases {