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:
co-authored by
Gabriel Mabille
parent
67f50478d9
commit
936dd05eac
@@ -3,6 +3,7 @@ package sync
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
@@ -13,6 +14,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/permreg"
|
||||
"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"
|
||||
)
|
||||
@@ -28,6 +30,7 @@ func ProvideRBACSync(acService accesscontrol.Service, tracer tracing.Tracer, per
|
||||
log: log.New("permissions.sync"),
|
||||
permRegistry: permRegistry,
|
||||
tracer: tracer,
|
||||
mapper: rbac.NewMapperRegistry(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +39,7 @@ type RBACSync struct {
|
||||
permRegistry permreg.PermissionRegistry
|
||||
log log.Logger
|
||||
tracer tracing.Tracer
|
||||
mapper rbac.MapperRegistry
|
||||
}
|
||||
|
||||
func (s *RBACSync) SyncPermissionsHook(ctx context.Context, ident *authn.Identity, _ *authn.Request) error {
|
||||
@@ -81,7 +85,8 @@ func (s *RBACSync) fetchPermissions(ctx context.Context, ident *authn.Identity)
|
||||
permissions := make([]accesscontrol.Permission, 0, 8)
|
||||
roles := ident.ClientParams.FetchPermissionsParams.Roles
|
||||
actions := ident.ClientParams.FetchPermissionsParams.AllowedActions
|
||||
if len(roles) > 0 || len(actions) > 0 {
|
||||
k8s := ident.ClientParams.FetchPermissionsParams.K8s
|
||||
if len(roles) > 0 || len(actions) > 0 || len(k8s) > 0 {
|
||||
for _, role := range roles {
|
||||
roleDTO, err := s.ac.GetRoleByName(ctx, ident.GetOrgID(), role)
|
||||
if err != nil && !errors.Is(err, accesscontrol.ErrRoleNotFound) {
|
||||
@@ -93,19 +98,11 @@ func (s *RBACSync) fetchPermissions(ctx context.Context, ident *authn.Identity)
|
||||
}
|
||||
}
|
||||
for _, action := range actions {
|
||||
scopes, ok := s.permRegistry.GetScopePrefixes(action)
|
||||
if !ok {
|
||||
s.log.Warn("Unknown action scopes", "action", action)
|
||||
continue
|
||||
}
|
||||
if len(scopes) == 0 {
|
||||
permissions = append(permissions, accesscontrol.Permission{Action: action})
|
||||
continue
|
||||
}
|
||||
for scope := range scopes {
|
||||
permissions = append(permissions, accesscontrol.Permission{Action: action, Scope: scope + "*"})
|
||||
}
|
||||
s.addPermissionsForAction(action, &permissions)
|
||||
}
|
||||
// Add K8s permissions
|
||||
k8sPermissions := s.translateK8sPermissions(ctx, k8s)
|
||||
permissions = append(permissions, k8sPermissions...)
|
||||
return permissions, nil
|
||||
}
|
||||
|
||||
@@ -117,6 +114,88 @@ func (s *RBACSync) fetchPermissions(ctx context.Context, ident *authn.Identity)
|
||||
return permissions, nil
|
||||
}
|
||||
|
||||
// addPermissionsForAction is a helper method that handles the common pattern of:
|
||||
// 1. Getting scope prefixes for an action
|
||||
// 2. Adding permissions with appropriate scopes
|
||||
// 3. Logging warnings for unknown actions
|
||||
func (s *RBACSync) addPermissionsForAction(action string, permissions *[]accesscontrol.Permission) {
|
||||
scopes, ok := s.permRegistry.GetScopePrefixes(action)
|
||||
if !ok {
|
||||
s.log.Warn("Unknown action scopes", "action", action)
|
||||
return
|
||||
}
|
||||
if len(scopes) == 0 {
|
||||
*permissions = append(*permissions, accesscontrol.Permission{Action: action})
|
||||
return
|
||||
}
|
||||
for scope := range scopes {
|
||||
*permissions = append(*permissions, accesscontrol.Permission{Action: action, Scope: scope + "*"})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *RBACSync) translateK8sPermissions(_ context.Context, k8sPerms []string) []accesscontrol.Permission {
|
||||
permissions := make([]accesscontrol.Permission, 0, len(k8sPerms))
|
||||
for _, k8sPerm := range k8sPerms {
|
||||
parts := strings.Split(k8sPerm, ":")
|
||||
if len(parts) != 2 {
|
||||
s.log.Warn("Invalid K8s permission format", "permission", k8sPerm)
|
||||
continue
|
||||
}
|
||||
groupResource := strings.Split(parts[0], "/")
|
||||
group := groupResource[0]
|
||||
verb := parts[1]
|
||||
switch {
|
||||
case len(groupResource) == 1:
|
||||
// Case group:verb
|
||||
resourceMappings := s.mapper.GetAll(group)
|
||||
if len(resourceMappings) == 0 {
|
||||
s.log.Warn("No mappings found for group", "group", group)
|
||||
continue
|
||||
}
|
||||
for _, mapping := range resourceMappings {
|
||||
if verb == "*" {
|
||||
actions := mapping.AllActions()
|
||||
for _, action := range actions {
|
||||
s.addPermissionsForAction(action, &permissions)
|
||||
}
|
||||
continue
|
||||
}
|
||||
action, ok := mapping.Action(verb)
|
||||
if !ok {
|
||||
s.log.Warn("Unknown K8s verb for group", "group", group, "verb", verb)
|
||||
continue
|
||||
}
|
||||
s.addPermissionsForAction(action, &permissions)
|
||||
}
|
||||
case len(groupResource) == 2:
|
||||
// Case group/resource:verb
|
||||
resource := groupResource[1]
|
||||
resourceMappings, ok := s.mapper.Get(group, resource)
|
||||
if !ok {
|
||||
s.log.Warn("Unknown K8s resource", "group", group, "resource", resource)
|
||||
continue
|
||||
}
|
||||
if verb == "*" {
|
||||
actions := resourceMappings.AllActions()
|
||||
for _, action := range actions {
|
||||
s.addPermissionsForAction(action, &permissions)
|
||||
}
|
||||
continue
|
||||
}
|
||||
action, ok := resourceMappings.Action(verb)
|
||||
if !ok {
|
||||
s.log.Warn("Unknown K8s verb", "group", group, "resource", resource, "verb", verb)
|
||||
continue
|
||||
}
|
||||
s.addPermissionsForAction(action, &permissions)
|
||||
default:
|
||||
s.log.Warn("Invalid K8s permission format", "permission", k8sPerm)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return permissions
|
||||
}
|
||||
|
||||
func cloudRolesToAddAndRemove(ident *authn.Identity) ([]string, []string, error) {
|
||||
// Since Cloud Admin/Editor/Viewer roles are not yet implemented one-to-one in the Grafana, it becomes a confusing experience for users,
|
||||
// therefore we are doing granular mapping of all available functionality in the Grafana temporary.
|
||||
|
||||
Reference in New Issue
Block a user