* OnGoing fixing cyclomatic complexity * Reduce cyclo complexity * Spaces
235 lines
7.1 KiB
Go
235 lines
7.1 KiB
Go
package authorizer
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/grafana/authlib/types"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
|
|
iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1"
|
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
|
"github.com/grafana/grafana/pkg/infra/log"
|
|
"github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer/storewrapper"
|
|
)
|
|
|
|
// TODO: Logs, Metrics, Traces?
|
|
|
|
// ParentProvider interface for fetching parent information of resources
|
|
type ParentProvider interface {
|
|
// HasParent checks if the given GroupResource has a parent folder
|
|
HasParent(gr schema.GroupResource) bool
|
|
// GetParent fetches the parent folder name for the given resource
|
|
GetParent(ctx context.Context, gr schema.GroupResource, namespace, name string) (string, error)
|
|
}
|
|
|
|
// ResourcePermissionsAuthorizer
|
|
type ResourcePermissionsAuthorizer struct {
|
|
accessClient types.AccessClient
|
|
parentProvider ParentProvider
|
|
logger log.Logger
|
|
}
|
|
|
|
var _ storewrapper.ResourceStorageAuthorizer = (*ResourcePermissionsAuthorizer)(nil)
|
|
|
|
func NewResourcePermissionsAuthorizer(
|
|
accessClient types.AccessClient,
|
|
parentProvider ParentProvider,
|
|
) *ResourcePermissionsAuthorizer {
|
|
return &ResourcePermissionsAuthorizer{
|
|
accessClient: accessClient,
|
|
parentProvider: parentProvider,
|
|
logger: log.New("iam.authorizer.resource-permissions"),
|
|
}
|
|
}
|
|
|
|
func isAccessPolicy(authInfo types.AuthInfo) bool {
|
|
return types.IsIdentityType(authInfo.GetIdentityType(), types.TypeAccessPolicy)
|
|
}
|
|
|
|
// AfterGet implements ResourceStorageAuthorizer.
|
|
func (r *ResourcePermissionsAuthorizer) AfterGet(ctx context.Context, obj runtime.Object) error {
|
|
authInfo, ok := types.AuthInfoFrom(ctx)
|
|
if !ok {
|
|
return storewrapper.ErrUnauthenticated
|
|
}
|
|
switch o := obj.(type) {
|
|
case *iamv0.ResourcePermission:
|
|
target := o.Spec.Resource
|
|
targetGR := schema.GroupResource{Group: target.ApiGroup, Resource: target.Resource}
|
|
|
|
parent := ""
|
|
// Fetch the parent of the resource
|
|
// Access Policies have global scope, so no parent check needed
|
|
if !isAccessPolicy(authInfo) && r.parentProvider.HasParent(targetGR) {
|
|
p, err := r.parentProvider.GetParent(ctx, targetGR, o.Namespace, target.Name)
|
|
if err != nil {
|
|
r.logger.Error("after get: error fetching parent", "error", err.Error(),
|
|
"namespace", o.Namespace,
|
|
"group", target.ApiGroup,
|
|
"resource", target.Resource,
|
|
"name", target.Name,
|
|
)
|
|
return err
|
|
}
|
|
parent = p
|
|
}
|
|
|
|
checkReq := types.CheckRequest{
|
|
Namespace: o.Namespace,
|
|
Group: target.ApiGroup,
|
|
Resource: target.Resource,
|
|
Verb: utils.VerbGetPermissions,
|
|
Name: target.Name,
|
|
}
|
|
res, err := r.accessClient.Check(ctx, authInfo, checkReq, parent)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !res.Allowed {
|
|
return fmt.Errorf(
|
|
"user cannot set permissions on resource %s/%s/%s: %w",
|
|
target.ApiGroup, target.Resource, target.Name, storewrapper.ErrUnauthorized,
|
|
)
|
|
}
|
|
return nil
|
|
default:
|
|
return fmt.Errorf("expected ResourcePermission, got %T: %w", o, storewrapper.ErrUnexpectedType)
|
|
}
|
|
}
|
|
|
|
func (r *ResourcePermissionsAuthorizer) beforeWrite(ctx context.Context, obj runtime.Object) error {
|
|
authInfo, ok := types.AuthInfoFrom(ctx)
|
|
if !ok {
|
|
return storewrapper.ErrUnauthenticated
|
|
}
|
|
switch o := obj.(type) {
|
|
case *iamv0.ResourcePermission:
|
|
target := o.Spec.Resource
|
|
targetGR := schema.GroupResource{Group: target.ApiGroup, Resource: target.Resource}
|
|
|
|
parent := ""
|
|
// Fetch the parent of the resource
|
|
// Access Policies have global scope, so no parent check needed
|
|
if !isAccessPolicy(authInfo) && r.parentProvider.HasParent(targetGR) {
|
|
p, err := r.parentProvider.GetParent(ctx, targetGR, o.Namespace, target.Name)
|
|
if err != nil {
|
|
r.logger.Error("before write: error fetching parent", "error", err.Error(),
|
|
"namespace", o.Namespace,
|
|
"group", target.ApiGroup,
|
|
"resource", target.Resource,
|
|
"name", target.Name,
|
|
)
|
|
return err
|
|
}
|
|
parent = p
|
|
}
|
|
|
|
checkReq := types.CheckRequest{
|
|
Namespace: o.Namespace,
|
|
Group: target.ApiGroup,
|
|
Resource: target.Resource,
|
|
Verb: utils.VerbSetPermissions,
|
|
Name: target.Name,
|
|
}
|
|
res, err := r.accessClient.Check(ctx, authInfo, checkReq, parent)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !res.Allowed {
|
|
return fmt.Errorf(
|
|
"user cannot set permissions on resource %s/%s/%s: %w",
|
|
target.ApiGroup, target.Resource, target.Name, storewrapper.ErrUnauthorized,
|
|
)
|
|
}
|
|
return nil
|
|
default:
|
|
return fmt.Errorf("expected ResourcePermission, got %T: %w", o, storewrapper.ErrUnexpectedType)
|
|
}
|
|
}
|
|
|
|
// BeforeCreate implements ResourceStorageAuthorizer.
|
|
func (r *ResourcePermissionsAuthorizer) BeforeCreate(ctx context.Context, obj runtime.Object) error {
|
|
return r.beforeWrite(ctx, obj)
|
|
}
|
|
|
|
// BeforeDelete implements ResourceStorageAuthorizer.
|
|
func (r *ResourcePermissionsAuthorizer) BeforeDelete(ctx context.Context, obj runtime.Object) error {
|
|
return r.beforeWrite(ctx, obj)
|
|
}
|
|
|
|
// BeforeUpdate implements ResourceStorageAuthorizer.
|
|
func (r *ResourcePermissionsAuthorizer) BeforeUpdate(ctx context.Context, obj runtime.Object) error {
|
|
return r.beforeWrite(ctx, obj)
|
|
}
|
|
|
|
// FilterList implements ResourceStorageAuthorizer.
|
|
func (r *ResourcePermissionsAuthorizer) FilterList(ctx context.Context, list runtime.Object) (runtime.Object, error) {
|
|
authInfo, ok := types.AuthInfoFrom(ctx)
|
|
if !ok {
|
|
return nil, storewrapper.ErrUnauthenticated
|
|
}
|
|
|
|
switch l := list.(type) {
|
|
case *iamv0.ResourcePermissionList:
|
|
var (
|
|
filteredItems []iamv0.ResourcePermission
|
|
err error
|
|
canViewFuncs = map[schema.GroupResource]types.ItemChecker{}
|
|
)
|
|
for _, item := range l.Items {
|
|
target := item.Spec.Resource
|
|
targetGR := schema.GroupResource{Group: target.ApiGroup, Resource: target.Resource}
|
|
|
|
// Reuse the same canView for items with the same resource
|
|
canView, found := canViewFuncs[targetGR]
|
|
|
|
if !found {
|
|
listReq := types.ListRequest{
|
|
Namespace: item.Namespace,
|
|
Group: target.ApiGroup,
|
|
Resource: target.Resource,
|
|
Verb: utils.VerbGetPermissions,
|
|
}
|
|
|
|
canView, _, err = r.accessClient.Compile(ctx, authInfo, listReq)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
canViewFuncs[targetGR] = canView
|
|
}
|
|
|
|
parent := ""
|
|
// Fetch the parent of the resource
|
|
// It's not efficient to do for every item in the list, but it's a good starting point.
|
|
// Access Policies have global scope, so no parent check needed
|
|
if !isAccessPolicy(authInfo) && r.parentProvider.HasParent(targetGR) {
|
|
p, err := r.parentProvider.GetParent(ctx, targetGR, item.Namespace, target.Name)
|
|
if err != nil {
|
|
// Skip item on error fetching parent
|
|
r.logger.Warn("filter list: error fetching parent, skipping item",
|
|
"error", err.Error(),
|
|
"namespace", item.Namespace,
|
|
"group", target.ApiGroup,
|
|
"resource", target.Resource,
|
|
"name", target.Name,
|
|
)
|
|
continue
|
|
}
|
|
parent = p
|
|
}
|
|
|
|
allowed := canView(item.Spec.Resource.Name, parent)
|
|
if allowed {
|
|
filteredItems = append(filteredItems, item)
|
|
}
|
|
}
|
|
l.Items = filteredItems
|
|
return l, nil
|
|
default:
|
|
return nil, fmt.Errorf("expected ResourcePermissionList, got %T: %w", l, storewrapper.ErrUnexpectedType)
|
|
}
|
|
}
|