ext jwt client: map k8s-style to rbac permissions (#106279)

* initial commit

* Proposal
Co-Authored-By: mohammad-hamid <mohammad.hamid@grafana.com>

* extend k8s-style mapper
- add tests

* address comments

* cleanup

* address comments

---------

Co-authored-by: Gabriel Mabille <gabriel.mabille@grafana.com>
This commit is contained in:
mohammad-hamid
2025-06-18 11:51:35 -04:00
committed by GitHub
co-authored by Gabriel Mabille
parent 67f50478d9
commit 936dd05eac
8 changed files with 319 additions and 38 deletions
@@ -17,6 +17,7 @@ import (
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
permreg "github.com/grafana/grafana/pkg/services/accesscontrol/permreg/test"
"github.com/grafana/grafana/pkg/services/authn"
rbac "github.com/grafana/grafana/pkg/services/authz/rbac"
"github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/org"
)
@@ -421,6 +422,135 @@ func TestRBACSync_ClearUserPermissionCacheHook(t *testing.T) {
}
}
func TestRBACSync_translateK8sPermissions(t *testing.T) {
type testCase struct {
name string
k8sPerms []string
expectedPerms []accesscontrol.Permission
expectedError error
}
tests := []testCase{
{
name: "should translate folder.grafana.app/folders:get to folders:read",
k8sPerms: []string{
"folder.grafana.app/folders:get",
},
expectedPerms: []accesscontrol.Permission{
{Action: "folders:read", Scope: "folders:uid:*"},
},
expectedError: nil,
},
{
name: "should translate resource with wildcard verb",
k8sPerms: []string{
"folder.grafana.app/folders:*",
},
expectedPerms: []accesscontrol.Permission{
{Action: "folders:read", Scope: "folders:uid:*"},
{Action: "folders:write", Scope: "folders:uid:*"},
{Action: "folders:delete", Scope: "folders:uid:*"},
{Action: "folders:create", Scope: "folders:uid:*"},
{Action: "folders.permissions:read", Scope: "folders:uid:*"},
{Action: "folders.permissions:write", Scope: "folders:uid:*"},
},
expectedError: nil,
},
{
name: "should handle invalid permission format",
k8sPerms: []string{
"invalid-format",
},
expectedPerms: []accesscontrol.Permission{},
expectedError: nil,
},
{
name: "should handle unknown resource",
k8sPerms: []string{
"folder.grafana.app/unknown:get",
},
expectedPerms: []accesscontrol.Permission{},
expectedError: nil,
},
{
name: "should handle unknown verb",
k8sPerms: []string{
"folder.grafana.app/folders:unknown",
},
expectedPerms: []accesscontrol.Permission{},
expectedError: nil,
},
{
name: "should handle group:verb format",
k8sPerms: []string{
"folder.grafana.app:get",
},
expectedPerms: []accesscontrol.Permission{
{Action: "folders:read", Scope: "folders:uid:*"},
},
expectedError: nil,
},
{
name: "should handle group:* format",
k8sPerms: []string{
"folder.grafana.app:*",
},
expectedPerms: []accesscontrol.Permission{
{Action: "folders:read", Scope: "folders:uid:*"},
{Action: "folders:write", Scope: "folders:uid:*"},
{Action: "folders:delete", Scope: "folders:uid:*"},
{Action: "folders:create", Scope: "folders:uid:*"},
{Action: "folders.permissions:read", Scope: "folders:uid:*"},
{Action: "folders.permissions:write", Scope: "folders:uid:*"},
},
expectedError: nil,
},
{
name: "should handle unknown group in group:verb format",
k8sPerms: []string{
"unknown.grafana.app:get",
},
expectedPerms: []accesscontrol.Permission{},
expectedError: nil,
},
{
name: "should handle unknown verb in group:verb format",
k8sPerms: []string{
"folder.grafana.app:unknownverb",
},
expectedPerms: []accesscontrol.Permission{},
expectedError: nil,
},
{
name: "should handle multiple group:verb permissions",
k8sPerms: []string{
"folder.grafana.app:get",
"folder.grafana.app:create",
},
expectedPerms: []accesscontrol.Permission{
{Action: "folders:read", Scope: "folders:uid:*"},
{Action: "folders:create", Scope: "folders:uid:*"},
},
expectedError: nil,
},
{
name: "should skip invalid permissions group/resource/additional/part:verb",
k8sPerms: []string{
"folder.grafana.app/folder/fold1/subresource:get",
},
expectedPerms: []accesscontrol.Permission{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := setupTestEnv(t)
perms := s.translateK8sPermissions(context.Background(), tt.k8sPerms)
assert.ElementsMatch(t, tt.expectedPerms, perms)
})
}
}
func setupTestEnv(t *testing.T) *RBACSync {
acMock := &acmock.Mock{
GetUserPermissionsFunc: func(ctx context.Context, siu identity.Requester, o accesscontrol.Options) ([]accesscontrol.Permission, error) {
@@ -446,11 +576,19 @@ func setupTestEnv(t *testing.T) *RBACSync {
},
}
permRegistry := permreg.ProvidePermissionRegistry(t)
require.NoError(t, permRegistry.RegisterPermission("folders:write", "folders:uid:"))
require.NoError(t, permRegistry.RegisterPermission("folders:create", "folders:uid:"))
require.NoError(t, permRegistry.RegisterPermission("folders:delete", "folders:uid:"))
require.NoError(t, permRegistry.RegisterPermission("folders.permissions:read", "folders:uid:"))
require.NoError(t, permRegistry.RegisterPermission("folders.permissions:write", "folders:uid:"))
s := &RBACSync{
ac: acMock,
log: log.NewNopLogger(),
tracer: tracing.InitializeTracerForTest(),
permRegistry: permRegistry,
mapper: rbac.NewMapperRegistry(),
}
return s
}