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:
Ryan McKinley
2025-04-09 13:05:37 +02:00
committed by GitHub
co-authored by Georges Chaudy
parent ceed824378
commit af8a70bbab
18 changed files with 466 additions and 83 deletions
+4 -39
View File
@@ -10,7 +10,6 @@ import (
"k8s.io/apiserver/pkg/registry/generic/registry"
"k8s.io/apiserver/pkg/registry/rest"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/apimachinery/utils"
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
@@ -19,7 +18,6 @@ import (
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/storage/unified/apistore"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
type DashboardStorage struct {
@@ -27,7 +25,7 @@ type DashboardStorage struct {
DashboardService dashboards.DashboardService
}
func (s *DashboardStorage) NewStore(dash utils.ResourceInfo, scheme *runtime.Scheme, defaultOptsGetter generic.RESTOptionsGetter, reg prometheus.Registerer) (grafanarest.Storage, error) {
func (s *DashboardStorage) NewStore(dash utils.ResourceInfo, scheme *runtime.Scheme, defaultOptsGetter generic.RESTOptionsGetter, reg prometheus.Registerer, permissions dashboards.PermissionsRegistrationService) (grafanarest.Storage, error) {
server, err := resource.NewResourceServer(resource.ResourceServerOptions{
Backend: s.Access,
Reg: reg,
@@ -47,6 +45,7 @@ func (s *DashboardStorage) NewStore(dash utils.ResourceInfo, scheme *runtime.Sch
optsGetter.RegisterOptions(dash.GroupResource(), apistore.StorageOptions{
EnableFolderSupport: true,
RequireDeprecatedInternalID: true,
Permissions: permissions.SetDefaultPermissionsAfterCreate,
})
store, err := grafanaregistry.NewRegistryStore(scheme, dash, optsGetter)
@@ -65,15 +64,7 @@ type storeWrapper struct {
func (s *storeWrapper) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
ctx = legacy.WithLegacyAccess(ctx)
meta, err := utils.MetaAccessor(obj)
if err != nil {
return nil, err
}
managerProperties, managerPresent := meta.GetManagerProperties()
isProvisioned := managerPresent && managerProperties.Kind != utils.ManagerKindUnknown
obj, err = s.Store.Create(ctx, obj, createValidation, options)
obj, err := s.Store.Create(ctx, obj, createValidation, options)
access := legacy.GetLegacyAccess(ctx)
if access != nil && access.DashboardID > 0 {
meta, _ := utils.MetaAccessor(obj)
@@ -82,10 +73,9 @@ func (s *storeWrapper) Create(ctx context.Context, obj runtime.Object, createVal
meta.SetDeprecatedInternalID(access.DashboardID) //nolint:staticcheck
}
}
meta, metaErr := utils.MetaAccessor(obj)
if metaErr == nil {
// Reconstruc the same UID as done at the storage level
// Reconstruct the same UID as done at the storage level
// https://github.com/grafana/grafana/blob/a84e96fba29c3a1bb384fdbad1c9c658cc79ec8f/pkg/registry/apis/dashboard/legacy/sql_dashboards.go#L287
// This is necessary because the UID generated during the creation via legacy storage is actually never stored in the database
// and the one returned here is wrong.
@@ -100,31 +90,6 @@ func (s *storeWrapper) Create(ctx context.Context, obj runtime.Object, createVal
return obj, metaErr
}
unstructuredMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
if err != nil {
return obj, err
}
unstructuredObj := &unstructured.Unstructured{Object: unstructuredMap}
user, err := identity.GetRequester(ctx)
if err != nil {
return obj, err
}
legacyDashboard, err := s.DashboardService.UnstructuredToLegacyDashboard(ctx, unstructuredObj, user.GetOrgID())
if err != nil {
return obj, err
}
// We only need these two parameters for SetDefaultPermissions
dto := &dashboards.SaveDashboardDTO{
User: user,
OrgID: user.GetOrgID(),
}
// Temporary approach to set default permissions until we have a proper method in place via k8s
s.DashboardService.SetDefaultPermissions(ctx, dto, legacyDashboard, isProvisioned)
return obj, nil
}
+7 -1
View File
@@ -74,6 +74,7 @@ type DashboardsAPIBuilder struct {
legacy *DashboardStorage
unified resource.ResourceClient
dashboardProvisioningService dashboards.DashboardProvisioningService
dashboardPermissions dashboards.PermissionsRegistrationService
scheme *runtime.Scheme
search *SearchHandler
dashStore dashboards.Store
@@ -94,6 +95,7 @@ func RegisterAPIService(
apiregistration builder.APIRegistrar,
dashboardService dashboards.DashboardService,
provisioningDashboardService dashboards.DashboardProvisioningService,
dashboardPermissions dashboards.PermissionsRegistrationService,
accessControl accesscontrol.AccessControl,
provisioning provisioning.ProvisioningService,
dashStore dashboards.Store,
@@ -116,6 +118,7 @@ func RegisterAPIService(
log: log.New("grafana-apiserver.dashboards"),
dashboardService: dashboardService,
dashboardPermissions: dashboardPermissions,
features: features,
accessControl: accessControl,
unified: unified,
@@ -389,6 +392,9 @@ func (b *DashboardsAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver
storageOpts := apistore.StorageOptions{
EnableFolderSupport: true,
RequireDeprecatedInternalID: true,
// Sets default root permissions
Permissions: b.dashboardPermissions.SetDefaultPermissionsAfterCreate,
}
// Split dashboards when they are large
@@ -468,7 +474,7 @@ func (b *DashboardsAPIBuilder) storageForVersion(
storage := map[string]rest.Storage{}
apiGroupInfo.VersionedResourcesStorageMap[dashboards.GroupVersion().Version] = storage
legacyStore, err := b.legacy.NewStore(dashboards, opts.Scheme, opts.OptsGetter, b.reg)
legacyStore, err := b.legacy.NewStore(dashboards, opts.Scheme, opts.OptsGetter, b.reg, b.dashboardPermissions)
if err != nil {
return err
}