K8s/Permissions: Enable a grant-permissions annotation action to set default permissions (#102527)
* create permissions * add key * lint * structure as a delayed callback * legacy API hook * merge main * wired up * and folders * watch repos * missing return statement * Set the correct permissions * add TestAfterCreatePermissionCreator * do not add perms on folder create * fix tests * add annotation on create * lint * lint * ensure we set permissions when the FT is disabled * remove custom folder_storage * fix lint * change default * lint * lint * fix: annotation * ensure permissions are added on folder legacy * remove folderstorage again * fix tests * add FT * undo change to folder * dashboard on create * remove annotation for folder * fix tests * fix prepare after rebase * fix tests * fix tests * fix tests * lint * address comments * add test for prepareObjectForStorage * add again skipIfMode as per comment --------- Co-authored-by: Georges Chaudy <chaudyg@gmail.com>
This commit is contained in:
co-authored by
Georges Chaudy
parent
ceed824378
commit
af8a70bbab
@@ -3,11 +3,14 @@ package dashboards
|
||||
import (
|
||||
"context"
|
||||
|
||||
authtypes "github.com/grafana/authlib/types"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/services/search/model"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
@@ -42,6 +45,9 @@ type DashboardService interface {
|
||||
|
||||
type PermissionsRegistrationService interface {
|
||||
RegisterDashboardPermissions(service accesscontrol.DashboardPermissionsService)
|
||||
|
||||
// Used to apply default permissions in unified storage after create
|
||||
SetDefaultPermissionsAfterCreate(ctx context.Context, key *resource.ResourceKey, id authtypes.AuthInfo, obj utils.GrafanaMetaAccessor) error
|
||||
}
|
||||
|
||||
// PluginService is a service for operating on plugin dashboards.
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/selection"
|
||||
|
||||
claims "github.com/grafana/authlib/types"
|
||||
"github.com/grafana/grafana-app-sdk/logging"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/gtime"
|
||||
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard"
|
||||
dashboardv0alpha1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1"
|
||||
@@ -41,6 +42,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/client"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
|
||||
dashboardclient "github.com/grafana/grafana/pkg/services/dashboards/service/client"
|
||||
@@ -1186,6 +1188,60 @@ func (dr *DashboardServiceImpl) GetDashboardsByPluginID(ctx context.Context, que
|
||||
return dr.dashboardStore.GetDashboardsByPluginID(ctx, query)
|
||||
}
|
||||
|
||||
// (sometimes) called by the k8s storage engine after creating an object
|
||||
func (dr *DashboardServiceImpl) SetDefaultPermissionsAfterCreate(ctx context.Context, key *resource.ResourceKey, id claims.AuthInfo, obj utils.GrafanaMetaAccessor) error {
|
||||
ctx, span := tracer.Start(ctx, "dashboards.service.SetDefaultPermissionsAfterCreate")
|
||||
defer span.End()
|
||||
|
||||
logger := logging.FromContext(ctx)
|
||||
|
||||
ns, err := request.NamespaceInfoFrom(ctx, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
user, err := identity.GetRequester(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uid, err := user.GetInternalID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var permissions []accesscontrol.SetResourcePermissionCommand
|
||||
if !dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesDashboards) {
|
||||
// legacy behavior
|
||||
permissions = []accesscontrol.SetResourcePermissionCommand{}
|
||||
if user.IsIdentityType(claims.TypeUser) {
|
||||
permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{
|
||||
UserID: uid, Permission: dashboardaccess.PERMISSION_ADMIN.String(),
|
||||
})
|
||||
}
|
||||
isNested := obj.GetFolder() != ""
|
||||
if !isNested || !dr.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) {
|
||||
permissions = append(permissions, []accesscontrol.SetResourcePermissionCommand{
|
||||
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()},
|
||||
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()},
|
||||
}...)
|
||||
}
|
||||
} else {
|
||||
if obj.GetFolder() != "" {
|
||||
return nil
|
||||
}
|
||||
permissions = []accesscontrol.SetResourcePermissionCommand{
|
||||
{UserID: uid, Permission: dashboardaccess.PERMISSION_ADMIN.String()},
|
||||
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_ADMIN.String()},
|
||||
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()},
|
||||
}
|
||||
}
|
||||
svc := dr.getPermissionsService(key.Resource == "folders")
|
||||
if _, err := svc.SetPermissions(ctx, ns.OrgID, obj.GetName(), permissions...); err != nil {
|
||||
logger.Error("Could not set default permissions", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) SetDefaultPermissions(ctx context.Context, dto *dashboards.SaveDashboardDTO, dash *dashboards.Dashboard, provisioned bool) {
|
||||
ctx, span := tracer.Start(ctx, "dashboards.service.setDefaultPermissions")
|
||||
defer span.End()
|
||||
@@ -1227,6 +1283,10 @@ func (dr *DashboardServiceImpl) SetDefaultPermissions(ctx context.Context, dto *
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) setDefaultFolderPermissions(ctx context.Context, cmd *folder.CreateFolderCommand, f *folder.Folder, provisioned bool) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx, span := tracer.Start(ctx, "dashboards.service.setDefaultFolderPermissions")
|
||||
defer span.End()
|
||||
|
||||
@@ -1792,7 +1852,6 @@ func (dr *DashboardServiceImpl) saveDashboardThroughK8s(ctx context.Context, cmd
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dashboard.SetPluginIDMeta(obj, cmd.PluginID)
|
||||
|
||||
out, err := dr.k8sclient.Update(ctx, obj, orgID)
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"gopkg.in/ini.v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
|
||||
dashboardv1alpha1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
@@ -28,6 +29,7 @@ import (
|
||||
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/client"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/folder/foldertest"
|
||||
@@ -2410,6 +2412,110 @@ func TestLegacySaveCommandToUnstructured(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestSetDefaultPermissionsAfterCreate(t *testing.T) {
|
||||
t.Run("Should set correct default permissions", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
rootFolder bool
|
||||
featureKubernetesDashboards bool
|
||||
expectedPermission []accesscontrol.SetResourcePermissionCommand
|
||||
}{
|
||||
{
|
||||
name: "without kubernetesDashboards feature in root folder",
|
||||
rootFolder: true,
|
||||
featureKubernetesDashboards: false,
|
||||
expectedPermission: []accesscontrol.SetResourcePermissionCommand{
|
||||
{UserID: 1, Permission: dashboardaccess.PERMISSION_ADMIN.String()},
|
||||
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()},
|
||||
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with kubernetesDashboards feature in root folder",
|
||||
rootFolder: true,
|
||||
featureKubernetesDashboards: true,
|
||||
expectedPermission: []accesscontrol.SetResourcePermissionCommand{
|
||||
{UserID: 1, Permission: dashboardaccess.PERMISSION_ADMIN.String()},
|
||||
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_ADMIN.String()},
|
||||
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "without kubernetesDashboards feature in subfolder",
|
||||
rootFolder: false,
|
||||
featureKubernetesDashboards: false,
|
||||
expectedPermission: []accesscontrol.SetResourcePermissionCommand{
|
||||
{UserID: 1, Permission: dashboardaccess.PERMISSION_ADMIN.String()},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with kubernetesDashboards feature in subfolder",
|
||||
rootFolder: false,
|
||||
featureKubernetesDashboards: true,
|
||||
expectedPermission: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
user := &user.SignedInUser{
|
||||
OrgID: 1,
|
||||
OrgRole: "Admin",
|
||||
UserID: 1,
|
||||
}
|
||||
ctx := request.WithNamespace(context.Background(), "default")
|
||||
ctx = identity.WithRequester(ctx, user)
|
||||
|
||||
// Setup mocks and service
|
||||
dashboardStore := &dashboards.FakeDashboardStore{}
|
||||
folderStore := foldertest.FakeFolderStore{}
|
||||
features := featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)
|
||||
if tc.featureKubernetesDashboards {
|
||||
features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesDashboards, featuremgmt.FlagNestedFolders)
|
||||
}
|
||||
|
||||
permService := acmock.NewMockedPermissionsService()
|
||||
permService.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil)
|
||||
|
||||
service := &DashboardServiceImpl{
|
||||
cfg: setting.NewCfg(),
|
||||
log: log.New("test-logger"),
|
||||
dashboardStore: dashboardStore,
|
||||
folderStore: &folderStore,
|
||||
features: features,
|
||||
dashboardPermissions: permService,
|
||||
folderPermissions: permService,
|
||||
dashboardPermissionsReady: make(chan struct{}),
|
||||
}
|
||||
service.RegisterDashboardPermissions(permService)
|
||||
|
||||
// Create test object
|
||||
key := &resource.ResourceKey{Group: "dashboard.grafana.app", Resource: "dashboards", Name: "test", Namespace: "default"}
|
||||
obj := &dashboardv1alpha1.Dashboard{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "dashboard.grafana.app/v0alpha1",
|
||||
},
|
||||
}
|
||||
meta, err := utils.MetaAccessor(obj)
|
||||
require.NoError(t, err)
|
||||
if !tc.rootFolder {
|
||||
meta.SetFolder("subfolder")
|
||||
}
|
||||
// Call the method
|
||||
err = service.SetDefaultPermissionsAfterCreate(ctx, key, user, meta)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify results
|
||||
if tc.expectedPermission == nil {
|
||||
permService.AssertNotCalled(t, "SetPermissions")
|
||||
} else {
|
||||
permService.AssertCalled(t, "SetPermissions", mock.Anything, mock.Anything, mock.Anything, tc.expectedPermission)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCleanUpDashboard(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
@@ -50,6 +50,7 @@ func (ss *FolderUnifiedStoreImpl) Create(ctx context.Context, cmd folder.CreateF
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out, err := ss.k8sclient.Create(ctx, obj, cmd.OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
Reference in New Issue
Block a user