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:
@@ -6,6 +6,21 @@ import (
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
)
|
||||
|
||||
// Mapping maps a verb to a RBAC action and a resource name to a RBAC scope.
|
||||
type Mapping interface {
|
||||
// action returns the action for the given verb.
|
||||
// If no action is found, it returns false.
|
||||
Action(verb string) (string, bool)
|
||||
// scope returns the scope for the given resource name.
|
||||
Scope(name string) string
|
||||
// prefix returns the scope prefix for the translation.
|
||||
Prefix() string
|
||||
// AllActions returns all the actions for the translation.
|
||||
AllActions() []string
|
||||
// HasFolderSupport returns true if the translation supports folders.
|
||||
HasFolderSupport() bool
|
||||
}
|
||||
|
||||
type translation struct {
|
||||
resource string
|
||||
attribute string
|
||||
@@ -13,19 +28,47 @@ type translation struct {
|
||||
folderSupport bool
|
||||
}
|
||||
|
||||
func (t translation) action(verb string) (string, bool) {
|
||||
func (t translation) Action(verb string) (string, bool) {
|
||||
action, ok := t.verbMapping[verb]
|
||||
return action, ok
|
||||
}
|
||||
|
||||
func (t translation) scope(name string) string {
|
||||
func (t translation) Scope(name string) string {
|
||||
return t.resource + ":" + t.attribute + ":" + name
|
||||
}
|
||||
|
||||
func (t translation) prefix() string {
|
||||
func (t translation) Prefix() string {
|
||||
return t.resource + ":" + t.attribute + ":"
|
||||
}
|
||||
|
||||
func (t translation) AllActions() []string {
|
||||
actions := make([]string, 0, len(t.verbMapping))
|
||||
actionsMap := make(map[string]bool)
|
||||
for _, action := range t.verbMapping {
|
||||
if actionsMap[action] {
|
||||
continue
|
||||
}
|
||||
actionsMap[action] = true
|
||||
actions = append(actions, action)
|
||||
}
|
||||
return actions
|
||||
}
|
||||
|
||||
func (t translation) HasFolderSupport() bool {
|
||||
return t.folderSupport
|
||||
}
|
||||
|
||||
// MapperRegistry is a registry of mappers that maps a group and resource to a translation.
|
||||
type MapperRegistry interface {
|
||||
// Get returns the permission mapper for the given group and resource.
|
||||
// If no translation is found, it returns false.
|
||||
Get(group, resource string) (Mapping, bool)
|
||||
// GetAll returns all the translations for the given group
|
||||
GetAll(group string) []Mapping
|
||||
}
|
||||
|
||||
type mapper map[string]map[string]translation
|
||||
|
||||
func newResourceTranslation(resource string, attribute string, folderSupport bool) translation {
|
||||
defaultMapping := func(r string) map[string]string {
|
||||
return map[string]string{
|
||||
@@ -50,10 +93,8 @@ func newResourceTranslation(resource string, attribute string, folderSupport boo
|
||||
}
|
||||
}
|
||||
|
||||
type mapper map[string]map[string]translation
|
||||
|
||||
func newMapper() mapper {
|
||||
return map[string]map[string]translation{
|
||||
func NewMapperRegistry() MapperRegistry {
|
||||
mapper := mapper(map[string]map[string]translation{
|
||||
"dashboard.grafana.app": {
|
||||
"dashboards": newResourceTranslation("dashboards", "uid", true),
|
||||
},
|
||||
@@ -77,19 +118,35 @@ func newMapper() mapper {
|
||||
folderSupport: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
return mapper
|
||||
}
|
||||
|
||||
func (m mapper) translation(group, resource string) (translation, bool) {
|
||||
func (m mapper) Get(group, resource string) (Mapping, bool) {
|
||||
resources, ok := m[group]
|
||||
if !ok {
|
||||
return translation{}, false
|
||||
return nil, false
|
||||
}
|
||||
|
||||
t, ok := resources[resource]
|
||||
if !ok {
|
||||
return translation{}, false
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return t, true
|
||||
return &t, true
|
||||
}
|
||||
|
||||
func (m mapper) GetAll(group string) []Mapping {
|
||||
resources, ok := m[group]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
translations := make([]Mapping, 0, len(resources))
|
||||
for _, t := range resources {
|
||||
translations = append(translations, &t)
|
||||
}
|
||||
|
||||
return translations
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ type Service struct {
|
||||
identityStore legacy.LegacyIdentityStore
|
||||
settings Settings
|
||||
|
||||
mapper mapper
|
||||
mapper MapperRegistry
|
||||
|
||||
logger log.Logger
|
||||
tracer tracing.Tracer
|
||||
@@ -93,7 +93,7 @@ func NewService(
|
||||
logger: logger,
|
||||
tracer: tracer,
|
||||
metrics: newMetrics(reg),
|
||||
mapper: newMapper(),
|
||||
mapper: NewMapperRegistry(),
|
||||
idCache: newCacheWrap[store.UserIdentifiers](cache, logger, tracer, longCacheTTL),
|
||||
permCache: newCacheWrap[map[string]bool](cache, logger, tracer, settings.CacheTTL),
|
||||
permDenialCache: newCacheWrap[bool](cache, logger, tracer, settings.CacheTTL),
|
||||
@@ -334,13 +334,13 @@ func (s *Service) validateSubject(ctx context.Context, subject string) (string,
|
||||
func (s *Service) validateAction(ctx context.Context, group, resource, verb string) (string, error) {
|
||||
ctxLogger := s.logger.FromContext(ctx)
|
||||
|
||||
t, ok := s.mapper.translation(group, resource)
|
||||
t, ok := s.mapper.Get(group, resource)
|
||||
if !ok {
|
||||
ctxLogger.Error("unsupport resource", "group", group, "resource", resource)
|
||||
return "", status.Error(codes.NotFound, "unsupported resource")
|
||||
}
|
||||
|
||||
action, ok := t.action(verb)
|
||||
action, ok := t.Action(verb)
|
||||
if !ok {
|
||||
ctxLogger.Error("unsupport verb", "group", group, "resource", resource, "verb", verb)
|
||||
return "", status.Error(codes.NotFound, "unsupported verb")
|
||||
@@ -585,17 +585,17 @@ func (s *Service) checkPermission(ctx context.Context, scopeMap map[string]bool,
|
||||
return true, nil
|
||||
}
|
||||
|
||||
t, ok := s.mapper.translation(req.Group, req.Resource)
|
||||
t, ok := s.mapper.Get(req.Group, req.Resource)
|
||||
if !ok {
|
||||
ctxLogger.Error("unsupport resource", "group", req.Group, "resource", req.Resource)
|
||||
return false, status.Error(codes.NotFound, "unsupported resource")
|
||||
}
|
||||
|
||||
if req.Name != "" && scopeMap[t.scope(req.Name)] {
|
||||
if req.Name != "" && scopeMap[t.Scope(req.Name)] {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if !t.folderSupport {
|
||||
if !t.HasFolderSupport() {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@@ -679,14 +679,14 @@ func (s *Service) listPermission(ctx context.Context, scopeMap map[string]bool,
|
||||
defer span.End()
|
||||
ctxLogger := s.logger.FromContext(ctx)
|
||||
|
||||
t, ok := s.mapper.translation(req.Group, req.Resource)
|
||||
t, ok := s.mapper.Get(req.Group, req.Resource)
|
||||
if !ok {
|
||||
ctxLogger.Error("unsupport resource", "group", req.Group, "resource", req.Resource)
|
||||
return nil, status.Error(codes.NotFound, "unsupported resource")
|
||||
}
|
||||
|
||||
var tree folderTree
|
||||
if t.folderSupport {
|
||||
if t.HasFolderSupport() {
|
||||
var err error
|
||||
tree, err = s.buildFolderTree(ctx, req.Namespace)
|
||||
if err != nil {
|
||||
@@ -699,7 +699,7 @@ func (s *Service) listPermission(ctx context.Context, scopeMap map[string]bool,
|
||||
if strings.HasPrefix(req.Action, "folders:") {
|
||||
res = buildFolderList(scopeMap, tree)
|
||||
} else {
|
||||
res = buildItemList(scopeMap, tree, t.prefix())
|
||||
res = buildItemList(scopeMap, tree, t.Prefix())
|
||||
}
|
||||
|
||||
span.SetAttributes(attribute.Int("num_folders", len(res.Folders)), attribute.Int("num_items", len(res.Items)))
|
||||
|
||||
@@ -1519,7 +1519,7 @@ func setupService() *Service {
|
||||
tracer := tracing.NewNoopTracerService()
|
||||
return &Service{
|
||||
logger: logger,
|
||||
mapper: newMapper(),
|
||||
mapper: NewMapperRegistry(),
|
||||
tracer: tracer,
|
||||
metrics: newMetrics(nil),
|
||||
idCache: newCacheWrap[store.UserIdentifiers](cache, logger, tracer, longCacheTTL),
|
||||
|
||||
Reference in New Issue
Block a user