support inherited permissions
This commit is contained in:
@@ -11,12 +11,15 @@ import (
|
|||||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/client-go/dynamic"
|
"k8s.io/client-go/dynamic"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
|
||||||
|
folderv1 "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
|
||||||
iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1"
|
iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1"
|
||||||
"github.com/grafana/grafana/pkg/api/dtos"
|
"github.com/grafana/grafana/pkg/api/dtos"
|
||||||
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
"github.com/grafana/grafana/pkg/services/team"
|
"github.com/grafana/grafana/pkg/services/team"
|
||||||
@@ -50,25 +53,39 @@ func (a *api) getResourcePermissionsFromK8s(ctx context.Context, namespace strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
resourcePermName := a.buildResourcePermissionName(resourceID)
|
resourcePermName := a.buildResourcePermissionName(resourceID)
|
||||||
|
|
||||||
resourcePermResource := dynamicClient.Resource(iamv0.ResourcePermissionInfo.GroupVersionResource()).Namespace(namespace)
|
resourcePermResource := dynamicClient.Resource(iamv0.ResourcePermissionInfo.GroupVersionResource()).Namespace(namespace)
|
||||||
unstructuredObj, err := resourcePermResource.Get(ctx, resourcePermName, metav1.GetOptions{})
|
unstructuredObj, err := resourcePermResource.Get(ctx, resourcePermName, metav1.GetOptions{})
|
||||||
if err != nil {
|
|
||||||
if k8serrors.IsNotFound(err) {
|
dto := make(getResourcePermissionsResponse, 0)
|
||||||
return getResourcePermissionsResponse{}, nil
|
|
||||||
}
|
if err != nil && !k8serrors.IsNotFound(err) {
|
||||||
return nil, fmt.Errorf("failed to get resource permission from k8s: %w", err)
|
return nil, fmt.Errorf("failed to get resource permission from k8s: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var resourcePerm iamv0.ResourcePermission
|
if unstructuredObj != nil {
|
||||||
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(unstructuredObj.Object, &resourcePerm); err != nil {
|
var resourcePerm iamv0.ResourcePermission
|
||||||
return nil, fmt.Errorf("failed to convert to typed resource permission: %w", err)
|
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(unstructuredObj.Object, &resourcePerm); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to convert to typed resource permission: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
directDTO, err := a.convertK8sResourcePermissionToDTO(&resourcePerm, namespace, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dto = append(dto, directDTO...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return a.convertK8sResourcePermissionToDTO(&resourcePerm, namespace)
|
inheritedDTO, err := a.getInheritedPermissions(ctx, namespace, resourceID, dynamicClient)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Warn("Failed to get inherited permissions from k8s API", "error", err, "resourceID", resourceID, "resource", a.service.options.Resource)
|
||||||
|
} else {
|
||||||
|
dto = append(dto, inheritedDTO...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dto, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *api) convertK8sResourcePermissionToDTO(resourcePerm *iamv0.ResourcePermission, namespace string) (getResourcePermissionsResponse, error) {
|
func (a *api) convertK8sResourcePermissionToDTO(resourcePerm *iamv0.ResourcePermission, namespace string, isInherited bool) (getResourcePermissionsResponse, error) {
|
||||||
permissions := resourcePerm.Spec.Permissions
|
permissions := resourcePerm.Spec.Permissions
|
||||||
if len(permissions) == 0 {
|
if len(permissions) == 0 {
|
||||||
return getResourcePermissionsResponse{}, nil
|
return getResourcePermissionsResponse{}, nil
|
||||||
@@ -107,7 +124,7 @@ func (a *api) convertK8sResourcePermissionToDTO(resourcePerm *iamv0.ResourcePerm
|
|||||||
Permission: permission,
|
Permission: permission,
|
||||||
Actions: actions,
|
Actions: actions,
|
||||||
IsManaged: true,
|
IsManaged: true,
|
||||||
IsInherited: false,
|
IsInherited: isInherited,
|
||||||
}
|
}
|
||||||
|
|
||||||
switch kind {
|
switch kind {
|
||||||
@@ -162,6 +179,115 @@ func getMapKeys(m map[string][]string) []string {
|
|||||||
return keys
|
return keys
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *api) getInheritedPermissions(ctx context.Context, namespace string, resourceID string, dynamicClient dynamic.Interface) (getResourcePermissionsResponse, error) {
|
||||||
|
switch a.service.options.Resource {
|
||||||
|
case "folders":
|
||||||
|
return a.getFolderHierarchyPermissions(ctx, namespace, resourceID, dynamicClient)
|
||||||
|
|
||||||
|
case "dashboards":
|
||||||
|
return a.getDashboardInheritedPermissions(ctx, namespace, resourceID, dynamicClient)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return getResourcePermissionsResponse{}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *api) getDashboardInheritedPermissions(ctx context.Context, namespace string, dashboardUID string, dynamicClient dynamic.Interface) (getResourcePermissionsResponse, error) {
|
||||||
|
dashboardsGVR := schema.GroupVersionResource{
|
||||||
|
Group: "dashboard.grafana.app",
|
||||||
|
Version: "v0alpha1",
|
||||||
|
Resource: "dashboards",
|
||||||
|
}
|
||||||
|
|
||||||
|
dashboardResource := dynamicClient.Resource(dashboardsGVR).Namespace(namespace)
|
||||||
|
unstructuredDash, err := dashboardResource.Get(ctx, dashboardUID, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
if k8serrors.IsNotFound(err) {
|
||||||
|
return getResourcePermissionsResponse{}, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("failed to get dashboard from k8s: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
annotations := unstructuredDash.GetAnnotations()
|
||||||
|
parentFolderUID := annotations[utils.AnnoKeyFolder]
|
||||||
|
|
||||||
|
if parentFolderUID == "" {
|
||||||
|
return getResourcePermissionsResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.getFolderHierarchyPermissions(ctx, namespace, parentFolderUID, dynamicClient)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getFolderHierarchyPermissions gets permissions from a folder and all its parents
|
||||||
|
func (a *api) getFolderHierarchyPermissions(ctx context.Context, namespace string, folderUID string, dynamicClient dynamic.Interface) (getResourcePermissionsResponse, error) {
|
||||||
|
foldersGVR := schema.GroupVersionResource{
|
||||||
|
Group: "folder.grafana.app",
|
||||||
|
Version: "v1beta1",
|
||||||
|
Resource: "folders",
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /apis/folder.grafana.app/v1beta1/namespaces/{namespace}/folders/{folderUID}/parents
|
||||||
|
parentsResource := dynamicClient.Resource(foldersGVR).Namespace(namespace)
|
||||||
|
unstructuredResult, err := parentsResource.Get(ctx, folderUID, metav1.GetOptions{}, "parents")
|
||||||
|
if err != nil {
|
||||||
|
if k8serrors.IsNotFound(err) {
|
||||||
|
// Folder not found or no parents
|
||||||
|
return getResourcePermissionsResponse{}, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("failed to get folder parents from k8s: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var folderInfoList folderv1.FolderInfoList
|
||||||
|
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(unstructuredResult.Object, &folderInfoList); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to convert folder parents response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(folderInfoList.Items) == 0 {
|
||||||
|
return getResourcePermissionsResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
allInheritedPermissions := make(getResourcePermissionsResponse, 0)
|
||||||
|
resourcePermResource := dynamicClient.Resource(iamv0.ResourcePermissionInfo.GroupVersionResource()).Namespace(namespace)
|
||||||
|
|
||||||
|
for _, parentFolder := range folderInfoList.Items {
|
||||||
|
if parentFolder.Detached {
|
||||||
|
a.logger.Debug("Skipping detached parent folder", "folderName", parentFolder.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if parentFolder.Name == folderUID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
parentPermName := a.buildResourcePermissionName(parentFolder.Name)
|
||||||
|
|
||||||
|
unstructuredObj, err := resourcePermResource.Get(ctx, parentPermName, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
if k8serrors.IsNotFound(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
a.logger.Warn("Failed to get parent folder permission from k8s", "error", err, "parentFolder", parentFolder.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var parentResourcePerm iamv0.ResourcePermission
|
||||||
|
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(unstructuredObj.Object, &parentResourcePerm); err != nil {
|
||||||
|
a.logger.Warn("Failed to convert parent folder permission", "error", err, "parentFolder", parentFolder.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
inheritedDTO, err := a.convertK8sResourcePermissionToDTO(&parentResourcePerm, namespace, true)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Warn("Failed to convert parent folder permissions to DTO", "error", err, "parentFolder", parentFolder.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
allInheritedPermissions = append(allInheritedPermissions, inheritedDTO...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return allInheritedPermissions, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (a *api) buildResourcePermissionName(resourceID string) string {
|
func (a *api) buildResourcePermissionName(resourceID string) string {
|
||||||
return fmt.Sprintf("%s-%s-%s", a.getAPIGroup(), a.service.options.Resource, resourceID)
|
return fmt.Sprintf("%s-%s-%s", a.getAPIGroup(), a.service.options.Resource, resourceID)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user