From b5a50e7772aa0a49cb8f00f04d371792e2673f62 Mon Sep 17 00:00:00 2001 From: Gabriel MABILLE Date: Thu, 20 Nov 2025 15:51:50 +0100 Subject: [PATCH] `grafana-iam`: Use the UniStore as the default store (#113614) * `grafana-iam`: Use the UniStore as the default store * Refactor all instantiations * Remove enableDualWriter * Nit. dw is clear enough * Use the correct access control client --- pkg/registry/apis/iam/models.go | 15 ++- pkg/registry/apis/iam/register.go | 212 ++++++++++++++---------------- 2 files changed, 112 insertions(+), 115 deletions(-) diff --git a/pkg/registry/apis/iam/models.go b/pkg/registry/apis/iam/models.go index c960b7a8655..309b266ccb4 100644 --- a/pkg/registry/apis/iam/models.go +++ b/pkg/registry/apis/iam/models.go @@ -8,6 +8,10 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/registry/apis/iam/legacy" + "github.com/grafana/grafana/pkg/registry/apis/iam/serviceaccount" + "github.com/grafana/grafana/pkg/registry/apis/iam/sso" + "github.com/grafana/grafana/pkg/registry/apis/iam/team" + "github.com/grafana/grafana/pkg/registry/apis/iam/teambinding" "github.com/grafana/grafana/pkg/registry/apis/iam/user" "github.com/grafana/grafana/pkg/services/apiserver/builder" "github.com/grafana/grafana/pkg/services/authz/zanzana" @@ -42,7 +46,13 @@ type ExternalGroupMappingStorageBackend interface{ resource.StorageBackend } // This is used just so wire has something unique to return type IdentityAccessManagementAPIBuilder struct { // Stores - store legacy.LegacyIdentityStore + store legacy.LegacyIdentityStore + + userLegacyStore *user.LegacyStore + saLegacyStore *serviceaccount.LegacyStore + legacyTeamStore *team.LegacyStore + teamBindingLegacyStore *teambinding.LegacyBindingStore + ssoLegacyStore *sso.LegacyStore coreRolesStorage CoreRoleStorageBackend rolesStorage RoleStorageBackend resourcePermissionsStorage resource.StorageBackend @@ -78,7 +88,4 @@ type IdentityAccessManagementAPIBuilder struct { // Toggle for enabling authz management apis features featuremgmt.FeatureToggles - - // Toggle for enabling dual writer - enableDualWriter bool } diff --git a/pkg/registry/apis/iam/register.go b/pkg/registry/apis/iam/register.go index cc229f2ed4b..2f214fd0630 100644 --- a/pkg/registry/apis/iam/register.go +++ b/pkg/registry/apis/iam/register.go @@ -31,7 +31,6 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/registry/apis/iam/externalgroupmapping" "github.com/grafana/grafana/pkg/registry/apis/iam/legacy" - "github.com/grafana/grafana/pkg/registry/apis/iam/noopstorage" "github.com/grafana/grafana/pkg/registry/apis/iam/resourcepermission" "github.com/grafana/grafana/pkg/registry/apis/iam/serviceaccount" "github.com/grafana/grafana/pkg/registry/apis/iam/sso" @@ -76,8 +75,16 @@ func RegisterAPIService( authorizer := newIAMAuthorizer(accessClient, legacyAccessClient) registerMetrics(reg) + //nolint:staticcheck // not yet migrated to OpenFeature + enableAuthnMutation := features.IsEnabledGlobally(featuremgmt.FlagKubernetesAuthnMutation) + builder := &IdentityAccessManagementAPIBuilder{ store: store, + userLegacyStore: user.NewLegacyStore(store, accessClient, enableAuthnMutation), + saLegacyStore: serviceaccount.NewLegacyStore(store, accessClient, enableAuthnMutation), + legacyTeamStore: team.NewLegacyStore(store, legacyAccessClient, enableAuthnMutation), + teamBindingLegacyStore: teambinding.NewLegacyBindingStore(store, enableAuthnMutation), + ssoLegacyStore: sso.NewLegacyStore(ssoService), coreRolesStorage: coreRolesStorage, rolesStorage: rolesStorage, resourcePermissionsStorage: resourcepermission.ProvideStorageBackend(dbProvider), @@ -93,7 +100,6 @@ func RegisterAPIService( reg: reg, logger: log.New("iam.apis"), features: features, - enableDualWriter: true, dual: dual, unified: unified, userSearchClient: resource.NewSearchClient(dualwrite.NewSearchAdapter(dual), iamv0.UserResourceInfo.GroupResource(), unified, user.NewUserLegacySearchClient(userService), features), @@ -113,18 +119,16 @@ func NewAPIService( store := legacy.NewLegacySQLStores(dbProvider) resourcePermissionsStorage := resourcepermission.ProvideStorageBackend(dbProvider) resourceAuthorizer := gfauthorizer.NewResourceAuthorizer(accessClient) - noopStorage := noopstorage.ProvideStorageBackend() registerMetrics(reg) return &IdentityAccessManagementAPIBuilder{ - store: store, - display: user.NewLegacyDisplayREST(store), - resourcePermissionsStorage: resourcePermissionsStorage, - externalGroupMappingStorage: noopStorage, - logger: log.New("iam.apis"), - features: features, - zClient: zClient, - zTickets: make(chan bool, MaxConcurrentZanzanaWrites), - reg: reg, + store: store, + display: user.NewLegacyDisplayREST(store), + resourcePermissionsStorage: resourcePermissionsStorage, + logger: log.New("iam.apis"), + features: features, + zClient: zClient, + zTickets: make(chan bool, MaxConcurrentZanzanaWrites), + reg: reg, authorizer: authorizer.AuthorizerFunc( func(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) { // For now only authorize resourcepermissions resource @@ -183,8 +187,7 @@ func (b *IdentityAccessManagementAPIBuilder) AllowedV0Alpha1Resources() []string func (b *IdentityAccessManagementAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.APIGroupInfo, opts builder.APIGroupOptions) error { storage := map[string]rest.Storage{} - //nolint:staticcheck // not yet migrated to OpenFeature - enableAuthnMutation := b.features.IsEnabledGlobally(featuremgmt.FlagKubernetesAuthnMutation) + //nolint:staticcheck // not yet migrated to OpenFeature enableZanzanaSync := b.features.IsEnabledGlobally(featuremgmt.FlagKubernetesAuthzZanzanaSync) @@ -197,69 +200,63 @@ func (b *IdentityAccessManagementAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *ge }) teamResource := iamv0.TeamResourceInfo - teamLegacyStore := team.NewLegacyStore(b.store, b.legacyAccessClient, enableAuthnMutation) - storage[teamResource.StoragePath()] = teamLegacyStore - storage[teamResource.StoragePath("members")] = team.NewLegacyTeamMemberREST(b.store) + teamUniStore, err := grafanaregistry.NewRegistryStore(opts.Scheme, teamResource, opts.OptsGetter) + if err != nil { + return err + } + storage[teamResource.StoragePath()] = teamUniStore - if b.enableDualWriter { - teamStore, err := grafanaregistry.NewRegistryStore(opts.Scheme, teamResource, opts.OptsGetter) + if b.legacyTeamStore != nil { + dw, err := opts.DualWriteBuilder(teamResource.GroupResource(), b.legacyTeamStore, teamUniStore) if err != nil { return err } - teamDW, err := opts.DualWriteBuilder(teamResource.GroupResource(), teamLegacyStore, teamStore) - if err != nil { - return err - } - - storage[teamResource.StoragePath()] = teamDW + storage[teamResource.StoragePath()] = dw } + storage[teamResource.StoragePath("members")] = team.NewLegacyTeamMemberREST(b.store) + teamBindingResource := iamv0.TeamBindingResourceInfo - teamBindingLegacyStore := teambinding.NewLegacyBindingStore(b.store, enableAuthnMutation) - storage[teamBindingResource.StoragePath()] = teamBindingLegacyStore + teamBindingUniStore, err := grafanaregistry.NewRegistryStore(opts.Scheme, teamBindingResource, opts.OptsGetter) + if err != nil { + return err + } + storage[teamBindingResource.StoragePath()] = teamBindingUniStore - if b.enableDualWriter { - teamBindingStore, err := grafanaregistry.NewRegistryStore(opts.Scheme, teamBindingResource, opts.OptsGetter) + // Only teamBindingStore exposes the AfterCreate, AfterDelete, and BeginUpdate hooks + if enableZanzanaSync { + b.logger.Info("Enabling hooks for TeamBinding to sync to Zanzana") + teamBindingUniStore.AfterCreate = b.AfterTeamBindingCreate + teamBindingUniStore.AfterDelete = b.AfterTeamBindingDelete + teamBindingUniStore.BeginUpdate = b.BeginTeamBindingUpdate + } + + if b.teamBindingLegacyStore != nil { + dw, err := opts.DualWriteBuilder(teamBindingResource.GroupResource(), b.teamBindingLegacyStore, teamBindingUniStore) if err != nil { return err } - - teamBindingDW, err := opts.DualWriteBuilder(teamBindingResource.GroupResource(), teamBindingLegacyStore, teamBindingStore) - if err != nil { - return err - } - - // Only teamBindingStore exposes the AfterCreate, AfterDelete, and BeginUpdate hooks - if enableZanzanaSync { - b.logger.Info("Enabling hooks for TeamBinding to sync to Zanzana") - teamBindingStore.AfterCreate = b.AfterTeamBindingCreate - teamBindingStore.AfterDelete = b.AfterTeamBindingDelete - teamBindingStore.BeginUpdate = b.BeginTeamBindingUpdate - } - - storage[teamBindingResource.StoragePath()] = teamBindingDW + storage[teamBindingResource.StoragePath()] = dw } // User store registration userResource := iamv0.UserResourceInfo - legacyStore := user.NewLegacyStore(b.store, b.accessClient, enableAuthnMutation) - storage[userResource.StoragePath()] = legacyStore + userUniStore, err := grafanaregistry.NewRegistryStore(opts.Scheme, userResource, opts.OptsGetter) + if err != nil { + return err + } + storage[userResource.StoragePath()] = userUniStore - if b.enableDualWriter { - store, err := grafanaregistry.NewRegistryStore(opts.Scheme, userResource, opts.OptsGetter) - if err != nil { - return err - } + if enableZanzanaSync { + b.logger.Info("Enabling hooks for User to sync basic role assignments to Zanzana") + userUniStore.AfterCreate = b.AfterUserCreate + userUniStore.BeginUpdate = b.BeginUserUpdate + userUniStore.AfterDelete = b.AfterUserDelete + } - if enableZanzanaSync { - b.logger.Info("Enabling hooks for User to sync basic role assignments to Zanzana") - store.AfterCreate = b.AfterUserCreate - store.BeginUpdate = b.BeginUserUpdate - store.AfterDelete = b.AfterUserDelete - } - - dw, err := opts.DualWriteBuilder(userResource.GroupResource(), legacyStore, store) + if b.userLegacyStore != nil { + dw, err := opts.DualWriteBuilder(userResource.GroupResource(), b.userLegacyStore, userUniStore) if err != nil { return err } @@ -270,50 +267,46 @@ func (b *IdentityAccessManagementAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *ge storage[userResource.StoragePath("teams")] = user.NewLegacyTeamMemberREST(b.store) // Service Accounts store registration - serviceAccountResource := iamv0.ServiceAccountResourceInfo - saLegacyStore := serviceaccount.NewLegacyStore(b.store, b.accessClient, enableAuthnMutation) - storage[serviceAccountResource.StoragePath()] = saLegacyStore - - if b.enableDualWriter { - store, err := grafanaregistry.NewRegistryStore(opts.Scheme, serviceAccountResource, opts.OptsGetter) - if err != nil { - return err - } - - dw, err := opts.DualWriteBuilder(serviceAccountResource.GroupResource(), saLegacyStore, store) - if err != nil { - return err - } - - storage[serviceAccountResource.StoragePath()] = dw - } - - storage[serviceAccountResource.StoragePath("tokens")] = serviceaccount.NewLegacyTokenREST(b.store) - - if b.sso != nil { - ssoResource := legacyiamv0.SSOSettingResourceInfo - storage[ssoResource.StoragePath()] = sso.NewLegacyStore(b.sso) - } - - externalGroupMappingResource := iamv0.ExternalGroupMappingResourceInfo - externalGroupMappingLegacyStore, err := NewLocalStore(externalGroupMappingResource, apiGroupInfo.Scheme, opts.OptsGetter, b.reg, b.accessClient, b.externalGroupMappingStorage) + saResource := iamv0.ServiceAccountResourceInfo + saUniStore, err := grafanaregistry.NewRegistryStore(opts.Scheme, saResource, opts.OptsGetter) if err != nil { return err } - storage[externalGroupMappingResource.StoragePath()] = externalGroupMappingLegacyStore + storage[saResource.StoragePath()] = saUniStore - if b.enableDualWriter { - externalGroupMappingStore, err := grafanaregistry.NewRegistryStore(opts.Scheme, externalGroupMappingResource, opts.OptsGetter) + if b.saLegacyStore != nil { + dw, err := opts.DualWriteBuilder(saResource.GroupResource(), b.saLegacyStore, saUniStore) + if err != nil { + return err + } + storage[saResource.StoragePath()] = dw + } + + storage[saResource.StoragePath("tokens")] = serviceaccount.NewLegacyTokenREST(b.store) + + if b.ssoLegacyStore != nil { + ssoResource := legacyiamv0.SSOSettingResourceInfo + storage[ssoResource.StoragePath()] = b.ssoLegacyStore + } + + extGroupMappingResource := iamv0.ExternalGroupMappingResourceInfo + extGroupMappingUniStore, err := grafanaregistry.NewRegistryStore(opts.Scheme, extGroupMappingResource, opts.OptsGetter) + if err != nil { + return err + } + storage[extGroupMappingResource.StoragePath()] = extGroupMappingUniStore + + if b.externalGroupMappingStorage != nil { + extGroupMappingLegacyStore, err := NewLocalStore(extGroupMappingResource, apiGroupInfo.Scheme, opts.OptsGetter, b.reg, b.accessClient, b.externalGroupMappingStorage) if err != nil { return err } - externalGroupMappingDW, err := opts.DualWriteBuilder(externalGroupMappingResource.GroupResource(), externalGroupMappingLegacyStore, externalGroupMappingStore) + dw, err := opts.DualWriteBuilder(extGroupMappingResource.GroupResource(), extGroupMappingLegacyStore, extGroupMappingUniStore) if err != nil { return err } - - storage[externalGroupMappingResource.StoragePath()] = externalGroupMappingDW + storage[extGroupMappingResource.StoragePath()] = dw } //nolint:staticcheck // not yet migrated to OpenFeature @@ -351,7 +344,7 @@ func (b *IdentityAccessManagementAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *ge } //nolint:staticcheck // not yet migrated to OpenFeature if b.features.IsEnabledGlobally(featuremgmt.FlagKubernetesAuthzResourcePermissionApis) { - if err := b.UpdateResourcePermissionsAPIGroup(apiGroupInfo, opts, storage, b.enableDualWriter, enableZanzanaSync); err != nil { + if err := b.UpdateResourcePermissionsAPIGroup(apiGroupInfo, opts, storage, enableZanzanaSync); err != nil { return err } } @@ -364,11 +357,19 @@ func (b *IdentityAccessManagementAPIBuilder) UpdateResourcePermissionsAPIGroup( apiGroupInfo *genericapiserver.APIGroupInfo, opts builder.APIGroupOptions, storage map[string]rest.Storage, - enableDualWriter bool, enableZanzanaSync bool, ) error { - var store rest.Storage - // Create the legacy store first + uniStore, err := grafanaregistry.NewRegistryStore(opts.Scheme, iamv0.ResourcePermissionInfo, opts.OptsGetter) + if err != nil { + return err + } + storage[iamv0.ResourcePermissionInfo.StoragePath()] = uniStore + + if b.resourcePermissionsStorage == nil { + // No legacy storage configured, nothing more to do + return nil + } + legacyStore, err := NewLocalStore(iamv0.ResourcePermissionInfo, apiGroupInfo.Scheme, opts.OptsGetter, b.reg, b.accessClient, b.resourcePermissionsStorage) if err != nil { return err @@ -384,23 +385,12 @@ func (b *IdentityAccessManagementAPIBuilder) UpdateResourcePermissionsAPIGroup( legacyStore.AfterDelete = b.AfterResourcePermissionDelete } - // Set the default store to the legacy store - store = legacyStore - - if enableDualWriter { - // Create the dual write store (UniStore + LegacyStore) - uniStore, err := grafanaregistry.NewRegistryStore(apiGroupInfo.Scheme, iamv0.ResourcePermissionInfo, opts.OptsGetter) - if err != nil { - return err - } - - store, err = opts.DualWriteBuilder(iamv0.ResourcePermissionInfo.GroupResource(), legacyStore, uniStore) - if err != nil { - return err - } + dw, err := opts.DualWriteBuilder(iamv0.ResourcePermissionInfo.GroupResource(), legacyStore, uniStore) + if err != nil { + return err } - storage[iamv0.ResourcePermissionInfo.StoragePath()] = store + storage[iamv0.ResourcePermissionInfo.StoragePath()] = dw return nil }